Lessons from Migrating My Blog to Next.js 16
The struggles and insights from upgrading from Next.js 14 to 16
"A Weekend Should Be Enough"
The reasons for migrating a perfectly working blog were honestly two things: Next.js 14 security patches were winding down, and I wanted React 19's new features. But the real reason is that developer instinct to try new versions when they drop. I admit it.
"A weekend should be enough," I told myself. It took 5 days. (The weekend plus 3 extra weekdays.)
Hit a Wall at next.config
First change you run into: the config file. next.config.js to next.config.ts is now officially recommended. Writing config in TypeScript is nice, but some existing plugins don't provide type definitions, so I had to stick @ts-expect-error on a few. A bit unsettling.
Turbopack becoming the default bundler meant webpack configs moved to opt-in. My blog needed webpack for MDX plugins, and I lost an entire day on that.
params Becoming a Promise Was Annoying
Previously you'd access params.slug directly. Now params is a Promise. You write const { slug } = await params; and the type becomes Promise<{ slug: string }>. Seems trivial, but I had 12 dynamic routes and every single one needed updating.
Tried regex find-and-replace, blew up on edge cases, ended up fixing each one by hand. The biggest time sink in migrations isn't the big changes -- it's small changes scattered across a dozen files.
Turbopack Is Fast, But...
Dev server start went from 8 seconds to 1.2 seconds. That's genuinely great. HMR got noticeably faster too.
But ESM compatibility issues tripped me up. MDX libraries like rehype-pretty-code didn't play nice with Turbopack. Build succeeded but code highlighting didn't work at runtime. Ended up going hybrid: Turbopack for dev, webpack for production builds. Not perfect, but a realistic compromise. (Should I call this a compromise or a defeat?)
Images Changed Subtly Too
next/image with fill got stricter about needing position: relative on the parent. Omitting sizes now throws console warnings. Updated all 20 images. Tedious, but Lighthouse score went up 3 points, so it wasn't pointless.
The Numbers Tell the Story
Dev server cold start: 8s to 1.2s. Production build: 45s to 38s. LCP: 1.8s to 1.4s. The dev server speed improvement especially has a tangible impact on DX. "Run npm run dev and go get coffee" time is gone.
One Honest Regret
I have one. I skimmed the official migration guide and jumped straight into work. If I'd read it carefully end to end, I would have saved a day. Also, updating all dependencies at once was a mistake. Should have done them one at a time, verifying the build at each step. Update everything at once and you have no idea what broke.
Took 5 days, but glad I did it. Just abandon the "a weekend should be enough" optimism. Multiply your migration time estimate by 2.5 and you'll be in the right ballpark.