Every few months someone posts a tweet that reignites the debate: "Just use Next.js for everything." Or the opposite: "Next.js is over-engineered, stop the madness." The truth, predictably, is somewhere in the middle โ and it depends entirely on what you're building.
I've shipped production apps on both. Dashboards with millions of events in plain React. Marketing sites, e-commerce storefronts, and SaaS products with Next.js. Here's what I've actually learned, not what the documentation says you should think.
"The best framework is the one that solves your actual problem without introducing five new ones. Next.js is powerful. It's also opinionated. Know what you're signing up for."
The Core Difference Nobody Explains Well
React is a UI library. It renders components in the browser. That's it. Routing, data fetching, bundling, server-side rendering โ none of that is React. You wire it together yourself.
Next.js is a framework built on top of React that makes specific decisions for you: file-based routing, server-side rendering out of the box, an API layer, image optimization, font optimization, and since Next.js 13 โ a completely new mental model called the App Router with React Server Components baked in.
The critical thing to understand: Next.js does not replace React. It extends it. When you write a Next.js app, you're still writing React components. You're just operating within Next.js's conventions and gaining its infrastructure in exchange for its constraints.
The question isn't "is Next.js better than React?" That's like asking if a hammer is better than steel. The question is: does your project benefit from what Next.js adds, or do its constraints cost you more than its features save?
When Next.js Is the Right Call
SEO-Critical Apps
If your product needs to rank on Google โ a blog, a marketing site, an e-commerce storefront, a documentation site, a content platform โ Next.js is almost always the right answer. Server-side rendering and static generation mean search crawlers see fully rendered HTML, not an empty div waiting for JavaScript to execute.
Plain React SPAs can be indexed by Google, but it's inconsistent, slower, and you're fighting the platform. With Next.js, SEO is a first-class concern. You get <Head> management, automatic sitemap support via plugins, and the ability to generate static pages at build time that load in milliseconds.
For anything with public-facing pages that need organic search traffic, this alone makes the decision for you.
Full-Stack in One Repo
Next.js lets you write your API routes right alongside your frontend. For smaller teams or solo developers, this is a genuine productivity win. No separate Express server to deploy, no CORS configuration to debug, no separate repository to manage.
The App Router's Server Actions take this further โ you can write server-side mutations directly in your components without even creating an API route:
// app/contact/page.tsx โ Server Action example
async function submitContactForm(formData: FormData) {
'use server'
const name = formData.get('name') as string
const email = formData.get('email') as string
await sendEmail({ name, email })
await db.contacts.create({ data: { name, email } })
}
export default function ContactPage() {
return (
<form action={submitContactForm}>
<input name="name" />
<input name="email" />
<button>Submit</button>
</form>
)
}
That's a real database write with email sending, zero API layer, zero boilerplate. For internal tools and marketing pages, this removes a surprising amount of friction.
Built-in Performance Defaults
Next.js bundles serious performance infrastructure. The <Image> component handles lazy loading, format conversion (WebP/AVIF), and responsive sizing automatically. The <Font> component eliminates layout shift from web fonts. Code splitting happens at the route level without configuration. These are things you'd spend days configuring properly in a plain React setup.
When Plain React Wins
SPAs and Dashboards
Analytics dashboards. Admin panels. Internal tools. SaaS application interfaces behind a login screen. These pages don't need to be indexed by Google. There's no meaningful SEO benefit to server-rendering a chart that shows data specific to a logged-in user. The content is different for every user, cached behind auth, and changes in real time.
For these use cases, a plain React SPA (built with Vite, for example) is simpler to reason about, faster to build, and easier to deploy. You're not paying the complexity tax of the App Router for zero benefit.
When You Control the Backend
If your team already has a mature Node.js, Go, or Django API, adding Next.js API routes creates an awkward hybrid where your real business logic lives in one place and your frontend data-fetching lives in another. You end up with two servers to deploy, two sets of environment variables to manage, and two places to look when something breaks.
In this scenario, React with a proper API client (React Query, SWR) is cleaner. Your backend team owns the API, your frontend team owns the React app, and the separation is clean.
Simpler Deployment
A plain React SPA is a folder of static files. You can host it on S3, Cloudflare Pages, GitHub Pages, or any CDN for almost nothing. Deployment is npm run build and aws s3 sync. There's no server to provision, no Node.js runtime to maintain, no cold starts to worry about.
Next.js requires a Node.js server for SSR and Server Actions. Vercel makes this painless (it's their product), but self-hosting Next.js with proper caching, ISR support, and image optimization is genuinely complex. Factor this into your infrastructure decision.
The App Router: Game Changer or Complexity Overhead?
This is the most contested question in the Next.js ecosystem right now. The App Router (introduced in Next.js 13, stable in 14) is a fundamental rethink of how Next.js works. Pages Router was simple: every file in /pages is a route, getServerSideProps fetches data, done.
App Router introduces React Server Components, where components run on the server by default. Client components โ anything with state, effects, or browser APIs โ must be explicitly marked with 'use client'. Layouts, loading states, and error boundaries are now file-system conventions, not manual wiring.
The mental model shift is real. Teams that have shipped large Pages Router apps often find the migration disorienting. The benefits โ smaller JS bundles, direct database access from components, streaming responses โ are genuine. But so is the learning curve.
My take: For new projects, use the App Router. The benefits outweigh the learning investment, especially as the ecosystem matures. For existing Pages Router projects, migrate incrementally only when you have a specific performance problem the App Router solves. Don't migrate for the sake of migrating.
Performance: Server Components vs Client Bundles
The performance story of Server Components is compelling once you understand it. A Server Component runs on the server, fetches data, renders to HTML, and ships zero JavaScript to the browser. A Client Component ships its JavaScript, hydrates in the browser, and adds to your bundle size.
// Server Component โ runs on server, zero client JS
// app/products/page.tsx
async function ProductsPage() {
// Direct DB access โ no API needed, no useEffect, no loading state
const products = await db.products.findMany({
where: { active: true },
orderBy: { createdAt: 'desc' },
})
return (
<div>
{products.map(p => (
<ProductCard key={p.id} product={p} />
))}
</div>
)
}
// Client Component โ needs interactivity
// components/add-to-cart.tsx
'use client'
import { useState } from 'react'
export function AddToCart({ productId }: { productId: string }) {
const [added, setAdded] = useState(false)
return (
<button onClick={() => setAdded(true)}>
{added ? 'Added!' : 'Add to Cart'}
</button>
)
}
The rule of thumb: make everything a Server Component by default. Only reach for 'use client' when you need state, effects, browser APIs (localStorage, window), or event listeners. This keeps your JavaScript bundle lean and your initial page load fast.
In a well-structured Next.js App Router project, your core data fetching components send zero JavaScript to the browser. The interactive parts โ dropdowns, modals, cart buttons โ are isolated Client Components. This is a fundamentally better performance model than a React SPA where everything, whether interactive or not, ships as JavaScript.
The Decision Framework
| Criteria | Next.js | Plain React (Vite/CRA) |
|---|---|---|
| SEO needed | Yes โ built-in SSR/SSG | No โ client-rendered only |
| Marketing / content site | Strong choice | Overkill without SSR |
| Dashboard / admin tool | Works, but no advantage | Simpler, better fit |
| SaaS app (post-login) | Possible, adds complexity | Clean SPA is fine |
| E-commerce storefront | Yes โ SEO + performance | Avoid for public pages |
| Full-stack in one repo | Yes โ API routes + Server Actions | Need separate server |
| Hosting simplicity | Requires Node.js server | Static files, any CDN |
| Team familiarity | Learning curve for App Router | Standard React knowledge |
| Bundle size | Smaller with Server Components | Larger โ everything ships |
| Real-time features | Possible but needs WebSockets separately | Easier to integrate |
My Recommendation
Here's the decision I walk clients through:
- Building anything with public pages that need Google traffic? Use Next.js. No debate. The SEO and performance benefits are too significant to leave on the table.
- Building an internal tool, dashboard, or app where users are always logged in? Use Vite + React. You don't need SSR. Keep it simple.
- Building a SaaS product with both a marketing site and an app? Consider two repos โ Next.js for the marketing site/landing pages, React SPA for the app itself. Or use Next.js for everything and mark your authenticated routes as Client Components with dynamic rendering.
- Solo developer or small team wanting to move fast? Next.js wins on developer experience. One repo, one deployment, built-in API routes, and excellent Vercel integration.
Next.js is not a replacement for React โ it's a set of strong opinions about how to build production-grade web apps. If those opinions align with your project's needs, it saves you weeks of setup. If they don't, they'll slow you down. Know your requirements before you choose your framework.
If you're starting a new project and aren't sure which direction to go, reach out โ I'm happy to talk through the architecture for your specific use case before you commit to a stack.