Next.js 16 in production: what we learned shipping 12 apps
Turbopack is no longer optional, cache components change how you think about data, and partial prerendering quietly became the default. Our field notes from twelve production upgrades.
Abdenassar Amimi
Founder & Lead Engineer
September 18, 2026
8 min read
We have upgraded or launched twelve production applications on Next.js 16 over the last two quarters — a mix of e-commerce, healthcare, and internal platforms. This post is the field notes we wish someone had handed us before the first migration: what actually changed, what broke, and what is genuinely faster.
Turbopack is the default, and that is a big deal
The headline change is that Turbopack is now the default bundler for both dev and build. On our largest codebase (a retail platform with ~2,400 modules), cold dev-server start went from 14s to just under 3s, and hot reload latency dropped from ~900ms to ~120ms. Nobody measures this in a demo — you feel it after the hundredth change of the day.
The practical migration note: most webpack loaders and plugins have no Turbopack equivalent yet. If your build leans on custom loaders (SVG-as-component tricks are the usual offender), move them to plain imports or a post-processing step first. Every migration blocker we hit was loader-related, not framework-related.
Cache components make you declare your data strategy
The new caching model flips the mental model: nothing is cached unless you say so. That sounds scary in a changelog, but in practice it kills the entire genre of "why is this page showing stale data" bugs that dynamic rendering used to produce.
// Data is uncached by default in Next.js 16 —
// you opt in per await boundary.
export default async function ProductPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const product = await getProduct(slug); // fresh every request
const reviews = await getReviews(slug); // cacheable, rarely changes
return (
<>
<ProductDetails product={product} />
<Reviews data={reviews} />
</>
);
}Our rule of thumb after twelve apps: cache lists and shells, never cache cart, session, or anything behind auth. We measured a 38% reduction in origin requests on the commerce site after marking review lists and category pages as cacheable — with zero staleness complaints, because cart and checkout paths stayed fully dynamic.
The params gotcha that will bite every codebase
Dynamic route params, searchParams, cookies, and headers are now asynchronous. Every page that read params directly needs an await. Codemods handle most of it, but watch for the sneaky ones: params passed into client components as props, and destructuring inside utility functions that pages call.
- →Grep for `params.` and `searchParams.` before you migrate — the codemod misses destructured calls in helper files.
- →Type them as `Promise<{ ... }>` in your page props or the compiler will find them for you, slowly.
- →Client components receiving params should receive plain values from the server instead — simpler than making them promise-aware.
Partial prerendering: the quiet win
The least flashy feature delivered the most measurable improvement. Pages with a static shell and small dynamic islands now stream the shell instantly while the dynamic parts resolve. On the healthcare portal, logged-in navigation felt instant even on 3G-simulated tests, because the app shell was prerendered and only the patient data streamed in.
The shell is the brand. If the frame of your app renders instantly, users forgive the data taking 300ms. If the frame itself takes 900ms, no spinner saves you.
What we would tell a team starting today
- →Budget your JavaScript. 16 does not fix a 1.2MB client bundle; it just delivers it faster.
- →Adopt Server Actions before the migration, not after — it shrinks your API surface and makes the new model natural.
- →Put Core Web Vitals in CI, not a dashboard you check quarterly. Every one of our twelve apps has a perf budget gate on the main branch.
- →Stay on the even-numbered release train for client work; let agencies like us find the odd-numbered bugs.
Is the upgrade worth it? For the six apps on Next.js 13/14 we touched, yes — measurably, in both developer velocity and shipped performance. For anything older, migrate straight to 16 in one hop; the intermediate versions only teach you deprecated patterns you will have to unlearn.
have-a-project-like-this
This is the kind of thinking we bring to client work. If it resonates, the discovery call is free.
Talk to uskeep-reading
Design Systems · 7 min
Design tokens: the contract between design and code
Most token systems fail silently — a Figma variable nobody renames, a hex value pasted into a component at 11pm. Here is the token architecture we use to keep design and code honest.
Product · 9 min
From MVP to v1: scoping your first 90 days
The MVP trap is real: products that launch as prototypes and stay that way. A field-tested framework for deciding what the first 90 days after launch should actually contain.