The most common mistake in this debate is treating Next.js and React as competitors. They aren't. React is a UI library for building components. Next.js is a full framework that uses React and adds routing, rendering strategies, and a build system on top. Choosing "Next.js vs React" is really choosing "framework vs library" — the same way you'd choose between Django and raw Python. The real question isn't "which is better" — it's "do I need the framework, or just the library?"
What React gives you on its own
React alone is a rendering engine for components and state. It's deliberately unopinionated about everything else. To ship a real app with plain React — most commonly scaffolded with Vite — you still assemble the rest yourself:
- A router (React Router or TanStack Router)
- A data-fetching layer (TanStack Query, SWR, or your own)
- A build and bundling setup (Vite handles this well)
- A rendering strategy — usually client-side only
This is a perfectly good choice for apps that live behind a login: internal dashboards, admin panels, and SaaS tools where SEO is irrelevant and the first paint can be a loading spinner. You keep full control, a smaller mental model, and a build that's fast and predictable. The cost is that you're responsible for the architectural decisions Next.js would have made for you.
What Next.js adds
Next.js bundles the decisions most teams would make anyway, and standardizes them. You get file-based routing, server-side rendering and static generation, image and font optimization, and API routes in one coherent package. Server Components and streaming let you render on the server by default and ship less JavaScript to the browser — the client only hydrates what actually needs interactivity.
The payoff is concentrated in two areas: SEO and performance on first load. If Google needs to index your pages, or if users arrive cold on marketing and content pages, server rendering is the difference between content that's visible instantly and content that appears only after a JavaScript bundle downloads and executes. For public-facing products, that gap directly affects both search ranking and conversion.
When to choose plain React (Vite)
Reach for plain React when:
- The app is behind auth and SEO is irrelevant. Dashboards, internal tools, admin consoles — no crawler will ever see them.
- You want minimal abstraction and full control. No framework conventions to fight, no server/client boundary to reason about.
- Your team already has routing and data patterns they like. You're not looking for a framework to make those calls for you.
- Build predictability matters. Vite is fast, stable, and rarely changes underneath you.
When to choose Next.js
Reach for Next.js when:
- SEO or social previews matter — marketing sites, blogs, documentation, e-commerce, any public content.
- You want server rendering, image optimization, and routing without wiring them yourself.
- You expect the app to grow and want conventions the whole team can follow instead of a bespoke architecture only two people understand.
- You're mixing public and private surfaces — a marketing site and an app in one codebase is a natural Next.js fit.
Performance
Head to head, the difference isn't raw speed once the app is loaded — both run the same React runtime in the browser. It's when the user sees content. Next.js sends server-rendered HTML, so first contentful paint happens before JavaScript executes. Plain React sends a near-empty HTML shell and paints only after the bundle downloads, parses, and hydrates. On a fast connection with a small app, nobody notices. On a large app or a slow mobile connection — which is most of the real world — the server-rendered version feels dramatically faster and scores far better on Core Web Vitals. Next.js also trims the JavaScript you ship via Server Components, which compounds the win.
Ecosystem and hosting
Both sit on the same enormous React and npm ecosystem, so component libraries and tooling work for either. The real divergence is operational.
- Next.js runs best on platforms that understand its server model — Vercel most seamlessly, though it self-hosts on Node or containers with more setup. Its conventions move fast: the App Router, Server Components, and caching model have all evolved significantly in recent versions, which means occasional migration work and a steeper learning curve for the server/client boundary.
- Plain React + Vite builds to static files that deploy literally anywhere — any CDN, any static host, for near-zero cost. It's more stable release-to-release, but pushes more architectural decisions onto you, and those decisions compound as the codebase grows.
Neither is wrong; they're just different operational commitments. This is the same kind of trade-off we walk through in choosing a tech stack.
Developer experience and the learning curve
Day-to-day, both feel like writing React, because both are writing React. The divergence is in what you have to hold in your head. Plain React with Vite has the smaller conceptual surface: components render in the browser, state lives on the client, and there's one execution environment to reason about. New developers are productive quickly, and debugging is straightforward because everything runs in one place.
Next.js asks more of you up front. The App Router, Server Components, server actions, and the caching layers introduce a server/client boundary you must keep clear in your head — which code runs where, what can be a Server Component, when data is fetched and cached, and how revalidation works. Once it clicks, it's genuinely powerful and a small team can build a lot fast. But the ramp is steeper, and the framework's rapid evolution means patterns you learned a year ago may already be superseded. Budget onboarding time accordingly, and lean on the conventions rather than fighting them.
A related point: because Next.js standardizes routing, data fetching, and file structure, a new engineer joining an existing Next.js codebase finds a familiar shape. A bespoke Vite + React app is only as legible as the team that assembled it — which can be excellent or can be a maze of one-off decisions. For teams that grow or rotate people, that standardization has real, compounding value.
Cost and migration
Cost tracks hosting and team time more than licensing — both are free and open source. Static React on a CDN is the cheapest thing to run; a Next.js app with heavy server rendering costs more in compute but saves engineering time you'd otherwise spend building framework features by hand.
On migration: going from plain React to Next.js is usually incremental and manageable — your components largely move over, and you layer routing and rendering on top. Going the other direction, ripping Next.js out, is more painful because you've likely leaned on its routing, data fetching, and Server Components. That asymmetry is worth weighing up front. If you're early and unsure, note that starting with Next.js and using it in a mostly client-rendered way keeps both doors open.
A note on the wider ecosystem
Neither choice locks you out of the modern React world — component libraries, state management, styling solutions, and testing tools all work with both. What differs is how much the framework decides for you. Next.js increasingly ships answers to questions plain React leaves open: data fetching and caching, where server work happens, how images and fonts load, how routes are defined. That's a feature when the defaults fit your product and a friction when they don't. With Vite and plain React you assemble your own stack from best-in-class pieces, which is liberating for a team with strong opinions and a burden for one that just wants sensible defaults and to start shipping. Neither philosophy is wrong; they suit different teams and different moments in a product's life.
Common mistakes teams make
A few patterns show up again and again when this decision goes wrong:
- Reaching for Next.js purely because it's popular, then never using server rendering because the whole app sits behind auth. You paid the framework's complexity tax for benefits you don't consume.
- Choosing plain React for a content-heavy public site, then bolting on a fragile custom pre-rendering setup to fix the SEO you gave up. You've rebuilt a worse version of what Next.js hands you for free.
- Fighting the App Router's conventions instead of learning them — treating Server Components as an obstacle rather than the point. Teams that lean into the model move fast; teams that resist it fight the framework daily.
- Ignoring the hosting reality until launch, then discovering the server model needs infrastructure the team didn't plan for.
The through-line: pick based on whether your product has a public, indexable, cold-load surface — not on what's trending. Match the tool to the job and both of these are excellent.
The verdict
For most client products with any public surface, we reach for Next.js — the SEO and performance defaults are worth the opinionated framework, and the conventions pay off as the team grows. For internal tools and dashboards where no crawler will ever visit, plain React on Vite is faster to build, cheaper to host, and easier to reason about. The right answer follows the product, not the trend. If your product is heading to mobile too, our take on React Native vs Flutter covers how to keep a shared TypeScript stack across web and app.
If you're weighing the two for a specific build and want a second opinion grounded in your actual constraints, let's talk.
Originally published on the Doktouri Agency blog. We build web, mobile, SaaS, and AI products — let's talk.
Top comments (1)
This is helpful !