The Next.js Backlash Is Overblown (But Not Entirely Wrong)

The internet loves a villain, and lately that villain has been Next.js. The loudest criticisms—vendor lock-in, App Router complexity, confusing caching defaults, and a release cadence that feels like it never stops—are real enough to deserve attention. But the conclusion people jump to (“Next.js is broken” or “it’s all hype”) is too lazy. The truth is more interesting: Next.js has genuine architectural wins, and the discourse is partly right about where teams get burned.
Let’s separate what’s performative from what’s actionable—because if you’re building with Next.js right now, you should care about both.
What Next.js Gets Right: Modern Rendering as a Product, Not a Hack⌗
A lot of the heat around Next.js ignores the core reason it’s so dominant: it made modern React rendering patterns feel approachable. React Server Components (RSC), streaming server-side rendering, and a pragmatic approach to hybrid static/dynamic pages are not just buzzwords—they’re architectural options you can use without reinventing the tooling each time.
Take streaming SSR. Before frameworks mainstreamed it, teams either settled for slower first paint or built brittle custom stacks. Next.js’s model lets you stream content to the browser while the server continues working. That matters for user experience and perceived performance, especially for pages that depend on multiple asynchronous data sources. Even if you never optimize “like a performance blog,” streaming gives you headroom.
Then there’s incremental static regeneration (ISR). The basic promise is simple: generate pages ahead of time, but refresh them on a schedule or on demand. Practically, this gives you an escape hatch from the binary choice between “all static” and “all dynamic.” For marketing sites, documentation, and content-heavy apps, ISR is a sweet spot. You can keep most pages fast and cacheable while still updating without redeploying everything.
My opinionated takeaway: Next.js’s success isn’t just ecosystem gravity. It’s that it turned serious rendering primitives into a developer workflow. That’s the part the backlash keeps skipping.
The Vercel Lock-In Claim: Not Wrong, Just Misframed⌗
The “vendor lock-in” argument usually appears in two flavors.
The first is emotional: “If you use Next.js on Vercel, you’re trapped.” That’s not quite how it works. Next.js itself is a framework; you can run it elsewhere. But the second flavor is more concrete: Vercel’s platform features are so smooth—caching behavior, deployment workflow, edge/runtime integration—that they can encourage coupling to the assumptions of that environment.
Here’s a practical way to evaluate lock-in rather than doomscrolling tweets: identify which parts of your app depend on platform-specific behavior.
For example:
- If you rely heavily on Vercel’s caching knobs and runtime behaviors, you may need a migration plan that explicitly tests those assumptions on your target host.
- If you use route handlers and server actions in ways that map neatly to Vercel’s execution model, you might face subtle behavior differences elsewhere.
- If your CI/CD pipeline, environment variables, secrets management, or build caching are all tightly Vercel-shaped, you’ve locked in operational convenience—even if the code remains portable.
So yes: the criticism has teeth, but it’s not fatal by default. Treat deployment platform features as optional enhancements, not your entire strategy. If your architecture depends on “works only when hosted like this,” that’s not a Next.js problem—it’s an engineering risk you chose.
App Router Complexity: The Problem Isn’t App Router—It’s Mental Load⌗
The App Router critique is often presented as “it’s too complex.” That’s too vague to help. What’s actually happening is that the App Router pushes you into a different set of mental models than the old pages-based approach.
Instead of a single convention for routing and rendering, you now juggle:
- Server and client component boundaries
- Data fetching patterns (and their impact on caching)
- Streaming and suspense semantics
- Layouts, nested routes, and route-level configuration
If you’ve been building React apps for years, the conceptual shift feels manageable at first—and then the edge cases arrive.
A concrete example: imagine a team that moves a component into the client boundary to “fix a bug,” and suddenly the page stops streaming as expected or data fetching behavior changes. The app still works, but its performance characteristics and caching are no longer what the team thinks they are. The code compiles; the mental model doesn’t.
The best practical advice I can offer is to enforce architectural boundaries early:
- Make “server by default” a team norm.
- Treat client components as a scarce resource.
- Document where data fetching happens and what caching policy is expected.
- Add lightweight code review checklists for component boundary changes.
App Router isn’t inherently wrong. It rewards teams that think clearly about boundaries. The backlash exists because many teams adopted it without also upgrading their engineering discipline.
Caching Defaults: This Is the Criticism With the Most Real-World Bite⌗
If you want one place where the backlash stops being noise and starts being useful, it’s caching. Next.js can behave in ways that feel unintuitive—especially when teams assume “it works like React” or “it works like plain Node SSR.”
Two recurring pain points show up in real projects:
- Caching policy is not always obvious from the component tree. When caching is tied to route-level or fetch-level semantics, it’s easy for developers to misattribute cause and effect.
- The defaults can be surprising. Developers may deploy something that appears correct, then later notice updates not showing up when they expect, or differences between local behavior and production behavior.
To make this practical, here’s what good teams do:
- Decide up front which routes should be “cacheable,” “revalidated,” or “dynamic.”
- For each fetch call, be explicit about caching and revalidation intent. Don’t rely on vibes.
- Create a staging environment that mirrors production caching settings as closely as possible.
- Add regression tests for content freshness—especially for routes that depend on CMS updates or time-sensitive data.
If your docs are “thin” (and the ecosystem often is), you compensate with internal guidance. A one-page engineering doc—“our caching rules for Next.js”—beats a week of Slack debates.
This is where the backlash is genuinely right: caching behavior needs better mental models and better tooling support. But the solution isn’t abandoning Next.js; it’s getting disciplined about cache intent and verifying freshness.
Release Cadence: Fast Doesn’t Mean Reckless—But It Can Feel Like It⌗
Next.js evolves quickly. That’s not inherently bad—web frameworks should not ossify. But rapid release cadence can be brutal when breaking changes land and teams lack time or tooling to validate every upgrade.
The core issue isn’t speed; it’s unpredictability. When a framework changes frequently, teams need a stable upgrade pipeline:
- Pin versions in lockfiles and update in controlled batches.
- Run end-to-end tests for key routes (not just unit tests).
- Pay attention to deprecations and migration guides early, not during firefights.
- Keep an “upgrade budget” in your planning, like you would for infrastructure maintenance.
If you’re encountering “things break every time,” that’s often a process failure, not only a framework failure. Frameworks move. Your job is to insulate production from constant churn.
One pragmatic stance: upgrade regularly, but not always immediately. Move in small steps, measure, and roll forward with confidence. Teams that do this usually experience “steady improvement.” Teams that wait until they’re multiple versions behind experience “random catastrophe.”
So… Should You Use Next.js Anyway?⌗
Here’s the nuanced position I’d actually recommend: don’t treat the Next.js backlash as a signal to abandon the framework. Treat it as a prompt to be more intentional about how you build.
Next.js’s architectural strengths—server components, streaming SSR, hybrid rendering, ISR—are real capabilities that improve the baseline quality of many applications. But the criticisms around caching clarity, App Router mental load, and operational coupling to hosting platforms are legitimate risk factors if you ignore them.
If you want a simple decision checklist:
- Are you prepared to be explicit about caching policy? If not, fix that first.
- Do you have team norms for server/client boundaries? If not, codify them.
- Do you understand how your platform features map to deployment portability? If not, run a migration thought experiment.
- Do you have a predictable upgrade process? If not, build one.
Done well, Next.js becomes less of a “framework you tolerate” and more of a platform that helps you ship faster—without surrendering control over performance and delivery.
Conclusion: The Discourse Is Loud. The Engineering Is Where the Truth Lives.⌗
The Next.js backlash is overblown in its certainty—but not entirely wrong in its instincts. The framework’s architectural advances are worth serious credit. The pain points—caching confusion, App Router cognitive load, and platform coupling—are real enough to demand better practices, clearer documentation, and more careful team discipline.
In other words: Next.js isn’t the villain. But the backlash is a reminder that frameworks don’t remove engineering responsibility—they concentrate it.