The App Router arrived with a lot of promise and a lot of warnings. It went stable in 2023, and three years on the picture is clearer: some patterns the community evangelised early have held up, others were quietly walked back. Next.js 16, released in October 2025, settled several open questions — Turbopack became the default bundler and caching moved to an explicit, opt-in model. Here is what holds up in production today, and why.
Server components: yes, but with discipline
Server components are genuinely good for fetching data close to where it is rendered and for keeping heavy dependencies out of the browser bundle. The footgun is over-using them: if everything is a server component, you lose interactivity, and the "use client" boundaries you eventually add fragment your tree in awkward places.
A rule that works: server components for page layout, data fetching and presentational sections; client components for anything stateful or interactive. Push the client boundary as far down the tree as you can — a server-rendered product page with a small client-side "add to cart" button, not a client page that fetches everything in the browser. Pass only serialisable props across the boundary, and keep secrets on the server.
Streaming is real, and underused
Suspense boundaries plus async server components let slow data stream in while the rest of the page renders. Use this for dashboards and AI-heavy pages where one component is genuinely slow: render the shell and fast widgets immediately, wrap the slow panel in <Suspense> with a skeleton fallback, and let it arrive when ready. It is one of the few legitimately new things the App Router enables that the Pages Router could not, and it pairs naturally with streamed LLM responses.
Server actions: useful, with caveats
For form submissions and small mutations, server actions are simpler than building API routes. Two caveats:
- Security. Every server action is a reachable endpoint. Authenticate and authorise inside the action itself, validate input against a schema, and never rely on a button being hidden in the UI.
- Testing and reuse. Actions are harder to test in isolation, and your mobile app or partners cannot call them. Keep business logic in plain functions that the action calls, so it can be tested and reused.
For anything beyond simple CRUD, or any API with more than one client, tRPC or REST remains the better choice. Server actions are not a replacement for an API.
Caching: read the docs twice, then once more
Caching has gone through more revisions than any other part of the framework. Next.js 15 stopped caching fetch requests and GET route handlers by default. Next.js 16 adds Cache Components, enabled with the cacheComponents flag: dynamic code runs at request time unless you opt in, and you mark pages, components or functions as cacheable with the "use cache" directive and set lifetimes with cacheLife. revalidateTag() now takes a cache-life profile, and the new updateTag() gives server actions read-your-writes behaviour. The Next.js 16 release notes list every changed default.
Practical rules:
- Decide per route whether data is static, periodically fresh or per-request, and write that decision down.
- Tag cached data by entity (for example
product-123) so a mutation can invalidate precisely what changed. - Never cache anything that depends on the current user unless the user is part of the cache key.
- Re-test every cache-sensitive page after each upgrade.
Caching surprises remain one of the most common sources of production bugs in App Router projects, so this is where review time pays off most.
Turbopack and the build pipeline
Turbopack is now stable for both development and production builds and is the default bundler in Next.js 16; you can opt out with next build --webpack if a plugin still depends on webpack. Default to Turbopack on new projects. On migrations, audit your custom webpack configuration first, because that is where most of the porting effort lives.
Other changes worth planning for
- Middleware is now proxy.
middleware.tsis replaced byproxy.ts, which runs on the Node.js runtime. Keep it thin: redirects, rewrites and header checks, not business logic. - Async request APIs.
params,searchParams,cookies()andheaders()are asynchronous, so older code needs to await them. - React Compiler support. Now stable and built in, it handles much of the memoisation you used to write by hand. Enable it on a branch and profile before and after.
A production checklist
- Error boundaries (
error.tsx) at the route-segment level, plus a global one. - Loading states, through
loading.tsxor Suspense, for every route that fetches. - Tracing via the
instrumentation.tshook, with request IDs in your logs. - Environment variables validated at build time, and nothing secret prefixed with
NEXT_PUBLIC_. - Next.js and React security advisories monitored, with patch releases applied promptly.
Migrating from the Pages Router
You do not need a big-bang move. Both routers can live in the same app, so migrate route by route: start with simple, high-traffic pages, move shared layouts next, and leave complex authenticated flows until the team is comfortable with the caching model.
How we ship Next.js builds at Velura Labs
Our web app development service ships Next.js 16, React 19 and TypeScript with the patterns above as the default. For APIs, data and deployment, see backend & infrastructure. Talk to us if you are starting a new build or migrating from the Pages Router and want a sanity check of your architecture.
We ship work like this for clients in the US (California, Texas, Washington, New York), across Europe (France, Italy and the EU), the Gulf (UAE and Saudi Arabia) and India — with an India delivery base that keeps cost down and time-zone overlap high. Talk to us.