·7 min read

Next.js vs plain React for production apps: what 6 years of client work taught me

Next.jsReactArchitectureOpinion

"Should we use Next.js or just React?" is one of the most common questions I get from clients. After six years shipping both to production, my honest answer is that they're not really competitors — but the choice still matters, and getting it wrong is expensive. Here's how I actually decide.

They're not the same category

React is a library for building user interfaces. Next.js is a framework built on React that adds routing, server rendering, data fetching, bundling, and a deployment model. So "Next.js vs React" is really "Next.js vs React plus a pile of decisions you now have to make yourself." The question isn't which is better — it's whether you want those decisions made for you.

What plain React (with Vite) still wins at

  • Internal tools and dashboards behind a login, where SEO is irrelevant and you want the leanest possible setup.
  • Embedded widgets that mount into someone else's page — you don't want a framework's routing and rendering assumptions.
  • Highly interactive single-page apps — editors, design tools, dashboards — that are basically one big client-side application.
  • Learning and prototyping, where Vite's instant dev server and minimal config get you moving in seconds.

For these, I'll reach for React with Vite and a router, and I won't feel like I'm missing anything. Adding Next.js here is complexity you pay for and never use.

What Next.js gives you that you'd otherwise rebuild

The moment a project needs to be found in search or load fast on a first visit, plain React starts costing you. A client-only React app ships an empty HTML shell and fills it in with JavaScript — bad for SEO and for the first paint. Next.js renders on the server so the page arrives populated. Compare the data-fetching story:

// Plain React: fetch after the component mounts, on the client.
function Products() {
  const [products, setProducts] = useState([]);
  useEffect(() => {
    fetch('/api/products').then(r => r.json()).then(setProducts);
  }, []);
  // First render is empty. Crawlers and slow phones see nothing yet.
  return <List items={products} />;
}

// Next.js: fetch on the server, ship populated HTML.
export default async function Products() {
  const products = await db.product.findMany();
  // The HTML already contains the products. Fast, indexable.
  return <List items={products} />;
}

You *can* recreate server rendering, code splitting, and image optimization in a plain React setup. I've done it. But you're now maintaining infrastructure that Next.js gives you for free and keeps updated.

The SEO gap is usually the decider

For marketing sites, e-commerce, blogs, marketplaces, and anything that lives on organic traffic, I default to Next.js — not because it's fashionable, but because server-rendered, indexable pages with clean metadata and structured data are the difference between ranking and being invisible. This very portfolio is a statically exported Next.js site for exactly that reason.

The cost nobody mentions: complexity

Next.js is not free to adopt. The App Router, Server versus Client Components, caching behaviour, and the server/client boundary are real concepts your team has to learn. I've watched teams ship subtle bugs by treating a Server Component like a Client one. If your app genuinely doesn't need server rendering, that learning curve buys you nothing.

How I choose, in one paragraph

If the project needs SEO, fast first loads, or server-side data fetching, I use Next.js. If it's a login-walled tool, a widget, or a heavily interactive SPA where none of that matters, I use React with Vite and keep the stack lean. I don't pick the framework I like most; I pick the one whose strengths match what the project actually needs — and I'll tell a client honestly when the trendy choice is the wrong one.