Upgrading EMPEERYAL to Next.js 16: What Broke and What Helped
By Paul Peery · July 9, 2026 · 3 min read

I run EMPEERYAL on Next.js. When version 16 landed, I upgraded the whole site. This is what happened, step by step.
A quick note before you copy anything: version numbers and feature names change fast. I checked these against the official Next.js 16 blog post before publishing. You should do the same when you read this.
Why I bothered
My site was running fine. But three things pulled me in:
- Turbopack is now the default bundler, and it's marked stable for both dev and production.
- Cache Components and the
use cachedirective give me clearer control over what gets cached. - The React Compiler can cut out a lot of manual memoization work.
That felt worth an afternoon.
Step 1: The upgrade itself
I started with the codemod. Next ships a tool that handles most of the boring changes for you.
npx @next/codemod@latest upgrade latest
Then I ran a clean install and did a full build to see what screamed.
rm -rf node_modules .next
npm install
npm run build
My advice: do this on a branch. Don't upgrade on main and hope.
Step 2: What actually broke
A few real things tripped me up.
Old config options. Some settings I had for the old bundler no longer applied. The build warned me, and I deleted them. Read the warnings instead of skipping past them.
Custom webpack tweaks. I had a small webpack change from years ago. With Turbopack as the default, that config path was ignored. I had to find the Turbopack way to do the same thing, or drop it if I no longer needed it.
A couple of dependencies. One or two packages needed updates to play nice with the new React version. Nothing dramatic, but plan for it.
None of this was scary. It was mostly cleanup of stuff I'd forgotten about.
Step 3: Turbopack as the default
This was the smoothest part. Turbopack now runs by default, so I didn't add a flag or opt in.
My dev server felt snappy on restarts and hot reloads. My production build ran clean too.
On timings: my build went from [BUILD_TIME_BEFORE] to [BUILD_TIME_AFTER], and dev startup went from [DEV_START_BEFORE] to [DEV_START_AFTER]. I'm leaving those as placeholders on purpose. Measure your own site, because your numbers will differ from mine and from anyone else's blog post.
Step 4: Cache Components and use cache
This is the part I spent the most time on.
The use cache directive lets me mark a function, component, or file as cacheable. Instead of scattering cache logic around, I put the directive right where the work happens.
async function getDeals() {
'use cache'
const data = await fetchDeals()
return data
}
A few gotchas I hit:
- Think about what's really static. I moved my slow, rarely-changing data behind
use cacheand left the fresh stuff alone. - Watch your inputs. Cached functions care about their arguments. Pass in a clear, small set of inputs and the caching behaves.
- Test the stale case. Cache a thing, change the source, and confirm you see the update when you expect to. Do this before you trust it in production.
I didn't convert everything at once. I picked a couple of pages, checked the results, then expanded.
Step 5: Turning on the React Compiler
The React Compiler can handle memoization for you, so you write less useMemo and useCallback by hand.
I opted in through the Next config. After that, I ran the site and clicked through the pages that have the most interaction. Everything held up.
One honest note: the compiler is powerful, but it's not magic. I still read the docs on which patterns it supports. If your code does something odd, test that part carefully.
Would I do it again?
Yes. The upgrade took an afternoon, most of the breakage was old junk I should have cleaned up anyway, and the caching story is clearer now.
If you're on the fence: branch it, run the codemod, and give yourself an hour to poke around. You'll learn more from your own build than from any writeup, including this one.
Comments
No comments yet — be the first!