Next.js in 2026: honest practitioner take on what's working and what isn't

By Nayan Kyada · · 6 min read

Part of Next.js Performance

Next.js in 2026 is the most powerful it has ever been, and the most exhausting to keep up with. I've shipped production sites on every major version from 12 through 15, and right now the framework sits in an interesting place: the core bets — React Server Components, Partial Prerendering, the App Router — are paying off in measurable ways, but the operational complexity has grown faster than the documentation explaining it.

This is not a hot take. It's a practitioner reading from the data I have.

Where next.js in 2026 genuinely shines

RSC is finally the default mental model. The transition period where half your team was thinking in pages and the other half in server components is largely over for teams that committed early. On a recent marketing site with Sanity as the CMS, the entire content-fetching layer — GROQ queries, image URL building, structured data construction — lives in server components with zero client JavaScript for those paths. The bundle number tells the story: 38 kB first load JS on the homepage, and that's with GSAP animations on scroll. Server components made that achievable without heroic tree-shaking.

PPR is real and it changes how you think about dynamic content. Partial Prerendering shipped stable and I've had it in production on two sites since Q1. The pattern is straightforward: the shell — header, hero, static copy — gets prerendered to the CDN edge. Personalised or session-dependent content streams in behind a <Suspense> boundary. TTFB on the static shell is under 40 ms on Vercel's edge network. Perceived load is dramatically better than a fully dynamic page, and you don't pay the cold-start penalty on every request.

Turbopack in development is not nothing. On a medium-complexity codebase — roughly 80 route segments, Tailwind v4, a handful of MDX pages — local cold start dropped from about 8 seconds to under 2. Hot reload on a Tailwind class change is nearly instant. I was skeptical of the benchmarks when they launched but the day-to-day experience difference is real.

The metadata API and image pipeline are mature. The generateMetadata function, next/image with Sanity's CDN, and the JSON-LD pattern using <script type="application/ld+json"> in a server component compose cleanly. I'm not fighting the framework on SEO fundamentals any more, which I was with the Pages Router's <Head> juggling.

Where it still hurts

Caching is genuinely hard to reason about. The four-layer cache model — request memoisation, Data Cache, Full Route Cache, Router Cache — made sense when Vercel explained it at the keynote. In production it produces bugs that are difficult to reproduce. The most common one I hit: a Sanity webhook fires, revalidatePath runs, the route cache clears, but the Router Cache on the client holds stale data for the remainder of the 30-second window. Editors see the live update in Studio and then see the old version in the browser preview. You end up adding router.refresh() calls in client components as a patch. It works, but it's the kind of fix that signals the abstraction is leaking.

The 'use cache' directive that landed in Next.js 15 helps, because it makes caching explicit rather than implicit. But it also means you need to audit every existing fetch call to understand what behaviour you're actually getting in the new model versus what you assumed. On a mature codebase, that audit takes days.

Upgrade churn is a real cost, not a talking point. Next.js 13, 14, and 15 each required meaningful changes to how routing, caching, and component boundaries work. I track the time I spend on upgrades per client engagement. For a site running Sanity + Next.js + Vercel, a minor version bump is usually an afternoon. A major version bump with App Router changes has consistently been two to four days of work including testing, which you either absorb in the retainer or bill separately and have a conversation about. Some clients notice this. The framework's velocity is a feature for Vercel's competitive position, but it's a real cost for maintenance-phase projects.

Lock-in is not imaginary. The most powerful Next.js features — PPR, after(), Incremental Static Regeneration with on-demand revalidation at scale, the Edge Runtime — are designed and optimised for Vercel's infrastructure. You can self-host Next.js on a Node server or in a Docker container, and the core routing works fine. But PPR requires the edge runtime model that Vercel runs natively. after() for post-response work is polyfilled differently depending on the host. If a client asks me "what if we need to move off Vercel in two years", my honest answer is: the migration is possible but the bill for rewriting the infrastructure-coupled parts is real. That's a reasonable trade-off for most teams, but it should be an explicit decision, not an assumption.

The App Router's async patterns bite junior developers hard. async/await in server components, useEffect still available but often wrong, use client boundary placement, useFormState replacing useState for form mutations — the mental model requires understanding React's concurrent model reasonably well. I've onboarded three contract developers onto Next.js 15 projects this year and each one spent at least a week fighting hydration errors or misplaced 'use client' directives before the model clicked. The framework is not beginner-hostile, but it is complexity-hostile if you're not ready for it.

My actual position

I'm still choosing Next.js for every content site and marketing build I take on. The RSC + Sanity combination is genuinely the most productive setup I've found for the category of work I do. PPR has moved from "interesting experiment" to "default choice" on sites where the dynamic surface is bounded.

But I scope upgrade time into every maintenance contract, I document the Vercel dependency explicitly in proposals, and I don't reach for Next.js when a client has a Rails or Django backend team and wants a thin frontend — that's a better fit for Remix or even a simple SPA.

The framework earns its complexity for the right use case. The mistake is treating it as the default for every case.