Upgrading This Site to Next.js 16: What Actually Broke (and What I Skipped)
Part of Building This Site and Small Business Websites
By Paul Peery · July 8, 2026 · 4 min readUpdated August 12, 2026

Framework upgrades get written about in two dishonest modes: the changelog reworded ("everything's faster!") and the horror story ("it broke everything!"). Mine was neither. Upgrading this site to Next.js 16 was a day of mechanical work, one genuinely subtle behavior change that mattered a lot, and a couple of headline features I read about carefully and then deliberately didn't adopt. That last part is the piece upgrade posts never include.
The mechanical part: wide but shallow
Async request props. The biggest visible change: params and searchParams arrive as Promises now. Every page and layout that reads a slug or a query string needed an await — which on a site with blog posts, deal pages, tag archives, series, topic hubs, and an admin panel meant touching a lot of files. But it's the good kind of migration: the type system finds every site, the fix is the same everywhere, and when it compiles again you're done. An agentic coding tool chews through this kind of change well — this is exactly the "wide but mechanical" work AI is good for, with the type-checker as the referee.
Turbopack as the default bundler, for dev and production builds. For me this was the non-event it's supposed to be — builds got faster and nothing needed configuring, which after years of bundler migrations still feels suspicious. The one habit change: some build output and chunk naming looks different, so anything that parses build output (I later built a performance-budget script) should read the running app over HTTP rather than scraping build internals — those change shape between majors; the HTML doesn't.
Route types. After moving or adding routes, npx next typegen regenerates the typed-routes info — easy to forget, instant to fix, worth putting in your project notes the first time it confuses you.
The subtle part: partial rendering changed what a layout means
Here's the one that matters beyond my site. Next's partial rendering means a layout doesn't re-render on every navigation within its segment — the framework reuses it and renders only the page that changed. Great for speed. But it quietly breaks a pattern half the internet uses: auth checks that live in a layout.
My admin panel checked the admin role in its layout, and every page under it trusted that. Under partial rendering, that check simply doesn't run on every route change — the docs now say so explicitly and tell you to check close to the data instead. The fix was straightforward (every admin page now guards itself, with the session lookup cached so it still costs one query per request), but I want to be honest about the order of events: I didn't catch this during the upgrade. I caught it later, during a full security pass, by reading the framework's own documentation for the version I was actually running. Which leads to the best habit I took away from the whole exercise:
Read the docs that shipped in your node_modules. Not the website — the website documents the latest release. The Markdown files inside the installed package document your version. My project notes now open with an instruction to check them before writing framework-adjacent code, because a framework's behavior at version X is a fact about your codebase, not a fact you remember from version X-2.
What I deliberately didn't adopt (yet)
Cache Components and use cache. The new explicit caching model is genuinely interesting — and I skipped it. This site's publishing model already fits classic ISR: pages revalidate every five minutes, and my admin actions surgically purge exact paths when content changes. That machinery is tested, tuned, and — importantly — understood. Swapping a working, well-understood caching model for a new one mid-upgrade is how you end up debugging two migrations at once. It's on the list to revisit on its own, as its own project.
The React Compiler. Stable now, ships opt-in, and I left it off. The honest reason: this site barely has the problem the compiler solves. It's overwhelmingly server components — the interactive client surface is small by design — and I'm not hand-writing enough useMemo to feel the pain the compiler removes. Enabling it would mean re-verifying my most fiddly client components (form state, editor autosave) for a win I can't currently measure. When the client surface grows, I'll revisit with the perf numbers in hand.
There's a theme: an upgrade is not an adoption event. Take the new baseline, keep your working patterns, and adopt headline features one at a time when they solve a problem you actually have.
The checklist I'd hand you
- Read the release notes and the upgrade guide in the installed package.
- Let the type-checker drive the async-props migration; it finds every site.
- Re-run your route typegen after any route moves.
- Audit every place a layout does something you assume happens per-navigation — auth checks above all. Move those checks into pages or the data layer.
- Decide explicitly, in writing, which new features you're not adopting yet and why. Future-you needs the reasoning, not just the diff.
- Trust your test suite over your memory — mine caught more upgrade fallout than I did.
More write-ups from building this site — the performance work, the security pass that caught the layout lesson — live under Building this site. And if you'd rather someone else absorb framework churn for your business site, that's a service I offer.
Keep reading
All postsComments
No comments yet — be the first!
