“Should I use React or Next.js?” sounds like a simple framework comparison.
I don’t really see it that way.
Next.js uses React. So the decision is not usually React instead of Next.js.
The real decision is closer to this:
Do I need React mainly for the user interface?
or
Do I want a framework around React that also handles
routing, server rendering, data fetching, backend code,
caching, metadata and deployment concerns?
That distinction matters because I would make a very different choice for an internal admin dashboard than I would for an e-commerce site, SaaS product, public content platform or application that needs server-side data access.
In this guide, I’ll explain how I think about React vs Next.js today, including where Vite fits, how Server Components change the architecture, and when the extra features of Next.js are useful rather than just additional complexity.
React and Next.js Are Not the Same Type of Tool
React is a library for building user interfaces from components.
It gives us concepts such as:
- components
- props
- state
- hooks
- context
- transitions
- Suspense
React itself doesn’t force one particular application architecture.
Next.js is a framework built around React.
It makes more decisions for us and provides infrastructure around the React component model.
React
|
+-- Components
+-- State
+-- Hooks
+-- UI rendering
Next.js
|
+-- React
+-- Routing
+-- Server Components
+-- Server rendering
+-- Data fetching
+-- Route Handlers
+-- Server Functions
+-- Metadata
+-- Image/font optimizations
+-- Caching and revalidation
The React documentation describes React as a library for web and native user interfaces, while the Next.js documentation describes Next.js as a React framework for building full-stack web applications.
That difference is a much better starting point than saying “React is client-side and Next.js is server-side.” Modern React is more capable than that comparison suggests.
A React App Doesn’t Mean Create React App Anymore
For years, a lot of React tutorials started with:
npx create-react-app my-app
I wouldn’t start a new project that way now.
Create React App has been deprecated. The React team now recommends using a framework for new applications when that fits the project, or using a modern build tool when you intentionally want to build a client-side React application yourself.
For a straightforward React application, Vite is one option I would consider.
npm create vite@latest my-app -- --template react-ts
cd my-app
npm install
npm run dev
Vite gives us a development server and production build without requiring us to manually configure Webpack and Babel just to start writing React.
You can check the current setup in the official Vite guide.
Where I Would Use React with Vite
I still like a plain React application when the application is genuinely client-focused.
For example, imagine an internal operations dashboard.
Browser
|
v
React application
|
v
Existing REST / GraphQL API
|
v
Backend services
If the backend already exists and the React application mainly needs to authenticate users, call APIs and display an interactive interface, I may not need another full-stack framework between them.
Examples where I would seriously consider React with Vite include:
- admin dashboards
- internal business tools
- authenticated portals where SEO does not matter
- applications consuming an existing backend API
- embedded web interfaces
- highly interactive client applications
In those cases, keeping the frontend simple can be a feature rather than a limitation.
Where Next.js Starts Making More Sense
My decision starts moving toward Next.js when the application needs more than a browser UI.
For example:
- public pages that need strong search visibility
- server-side data access
- static and dynamic pages in the same project
- backend endpoints close to the frontend
- server-side authentication logic
- streaming and Suspense
- server-rendered content
- built-in metadata handling
At that point, I would rather use a framework that already understands those requirements than slowly build my own framework around a Vite application.
The App Router Is the Important Next.js Model Today
The old version of this article described Next.js routing using the pages directory.
The Pages Router still exists and is supported, so existing applications do not suddenly need to be rewritten.
For a new project, however, I would start by understanding the App Router.
A small project can look like this:
app/
├── layout.tsx
├── page.tsx
│
├── about/
│ └── page.tsx
│
├── products/
│ ├── page.tsx
│ └── [id]/
│ └── page.tsx
│
└── api/
└── health/
└── route.ts
Each page.tsx represents UI for a route.
Layouts let parts of the interface persist across multiple pages, and route.ts files can provide HTTP handlers where the application needs them.
Next.js currently keeps both the App Router and Pages Router documented, but identifies App Router as the newer model that supports features such as React Server Components.
Server Components Changed the React vs Next.js Discussion
This is probably the biggest thing missing from older React vs Next.js comparisons.
With Next.js App Router, components are Server Components by default.
That means a component can perform work on the server without its component JavaScript automatically becoming part of the browser bundle.
For example:
import { db } from '@/lib/db';
export default async function ProductsPage() {
const products = await db.product.findMany();
return (
<main>
<h1>Products</h1>
{products.map(product => (
<p key={product.id}>
{product.name}
</p>
))}
</main>
);
}
There is no useEffect here.
There is no separate browser request to /api/products just so the page can query data that already lives on the server.
Server Component
|
v
Database
|
v
Rendered result
|
v
Browser
React’s own Server Components documentation explains that Server Components can access server-side resources and that their implementation code is not sent to the browser.
For data-heavy public applications, that can be a very useful architecture.
Server Component Does Not Mean Everything Runs on the Server
A product page may still need an interactive quantity selector, modal, carousel or add-to-cart button.
That part belongs in a Client Component.
'use client';
import { useState } from 'react';
export function QuantitySelector() {
const [quantity, setQuantity] = useState(1);
return (
<button
onClick={() => setQuantity(quantity + 1)}
>
Quantity: {quantity}
</button>
);
}
The architecture can then look like:
Product Page
Server Component
|
+-- Product information
|
+-- Database access
|
+-- Price
|
+-- QuantitySelector
|
v
Client Component
I prefer thinking in terms of boundaries rather than trying to make the entire application either “server” or “client.”
Keep work on the server when it does not need browser interactivity, and move the interactive parts to Client Components.
Don’t Add ‘use client’ Everywhere
One easy way to lose much of the benefit of Server Components is to add 'use client' near the top of every component tree.
Once a component establishes a client boundary, the modules used by that client-side subtree become part of the client graph.
I keep the client boundary as small as practical.
ProductPage Server
│
├── ProductTitle Server
├── ProductDescription Server
├── ProductPrice Server
│
└── AddToCart Client
|
└── Quantity Client
That is very different from marking the entire page as a Client Component just because one button needs useState.
Data Fetching Is Another Major Difference
Consider a normal client-side React application.
A component might load products after rendering:
useEffect(() => {
fetch('/api/products')
.then(response => response.json())
.then(setProducts);
}, []);
That can be perfectly acceptable, especially for an authenticated application where the browser already owns most of the interaction.
But the request path becomes:
Browser loads JavaScript
|
v
React renders
|
v
Browser requests API
|
v
API queries database
|
v
Browser receives data
|
v
React renders data
With a Next.js Server Component, we can sometimes remove that extra browser-to-API step.
Server Component
|
v
Database
|
v
Rendered output
|
v
Browser
The Next.js documentation specifically notes that Server Components can access server-side resources directly, including databases, without creating an additional API endpoint only for the server itself to call.
That doesn’t mean APIs are obsolete.
If a mobile app, another backend, third-party integration or Client Component needs an HTTP interface, an API is still completely appropriate.
Next.js Can Also Provide Backend Endpoints
With the App Router, Route Handlers can implement HTTP endpoints.
For example:
// app/api/health/route.ts
export async function GET() {
return Response.json({
status: 'ok'
});
}
This makes Next.js useful for applications where the backend requirements are closely connected to the web application.
But I wouldn’t automatically move every backend responsibility into Next.js.
If I already have:
- a large Node.js API
- several mobile clients
- background workers
- message queues
- multiple external integrations
- independently deployed backend services
I may keep that backend separate and let Next.js act as the web application rather than forcing the entire architecture into one repository.
If you’re building a separate backend, my production-ready NestJS REST API guide covers that style of architecture in more detail.
What About Server Functions?
Modern React also gives frameworks a server function model.
A function marked with 'use server' can run on the server and be invoked through React’s server-function mechanism.
export async function createContact(
formData: FormData
) {
'use server';
const email = formData.get('email');
// Validate input.
// Save data.
// Return result.
}
One terminology detail is worth remembering: 'use server' does not mean “this file contains Server Components.”
React uses 'use server' for Server Functions. Server Components themselves do not need a 'use server' directive.
The React documentation makes that distinction explicitly.
React vs Next.js for SEO
I try not to reduce this discussion to “React has bad SEO and Next.js has good SEO.”
That’s too simplistic.
A client-rendered React application can be indexed, and React itself can participate in server rendering through frameworks.
The practical difference is that Next.js gives us a much more integrated path for rendering content before or during the request and managing page metadata.
For something like:
- a marketing website
- an e-commerce catalogue
- a documentation site
- a public SaaS website
- content pages that need organic search traffic
I would rather start with an architecture where the important content can reach the browser as rendered output instead of depending entirely on a second client-side render cycle.
Server rendering isn’t only about SEO either. The React team points out that rendering on the server can also reduce how much JavaScript users need before seeing useful content.
Static Rendering and Dynamic Rendering Can Live Together
One thing I find useful about a framework such as Next.js is that the whole application doesn’t need one rendering strategy.
Different routes have different requirements.
/about
|
+-- changes rarely
/products
|
+-- data can be cached/revalidated
/account
|
+-- user-specific
/checkout
|
+-- highly dynamic
I don’t want to force all four routes into exactly the same rendering model just because they belong to the same application.
This is one reason I think the old “CSR vs SSR” comparison is no longer enough to explain modern React architecture.
Caching Is Powerful, but It Adds Another Thing to Understand
This is one area where Next.js can be both helpful and confusing.
Once a framework starts doing server rendering, prerendering and revalidation, I need to understand when my data is fresh and when a cached result may be reused.
For a product catalogue, caching may be exactly what I want.
For a user’s current account balance, accidentally treating the data as static would obviously be a very different problem.
So I don’t treat caching as a checkbox called “performance.”
I treat it as part of the application’s data model:
- How fresh does this data need to be?
- Is it shared between users?
- Can it be generated ahead of time?
- What should cause revalidation?
- What happens if the underlying data changes?
Next.js gives us more tools here than a plain client application, but those tools also create decisions we need to understand.
React with Vite Can Be Simpler to Deploy
Deployment is one place where a client-only React application has a real simplicity advantage.
A typical Vite build produces static assets:
npm run build
dist/
├── index.html
└── assets/
Those files can be served from static hosting or a CDN.
Browser
|
v
CDN / Static Hosting
|
v
React application
There is no React application server to operate if the app doesn’t require one.
If all I need is a client application talking to an existing API, that simplicity is valuable.
Next.js Does Not Automatically Mean Vercel
Another misconception I sometimes see is that choosing Next.js means you must host the application on Vercel.
You don’t.
Next.js can run on infrastructure that supports Node.js or containers, and it can also support static export for applications that don’t require server features.
Vercel can provide a convenient deployment experience because it develops Next.js, but hosting and framework choice are separate decisions.
Where Redux Fits
Choosing Next.js doesn’t automatically eliminate client-side state management.
Server Components can remove some state that previously existed only because the browser had to fetch and hold server data.
But genuinely client-side state still exists.
Examples include:
- complex editing state
- multi-step workflows
- local UI state shared across distant components
- some offline workflows
If Redux is actually useful for that state, I would still use it.
I just wouldn’t use Redux as a default place to copy every piece of server data into the browser.
If you need Redux, my Redux Toolkit tutorial covers the modern approach rather than the old createStore style.
What About React Hooks?
Hooks still matter in both approaches, but mainly where client-side behaviour is required.
A Client Component can still use:
useStateuseEffectuseReduceruseRefuseTransitionuseDeferredValue
A Server Component cannot use browser-oriented state and effect hooks because it isn’t running as an interactive component in the browser.
If you want a reference for the current hooks ecosystem, see my React Hooks Cheat Sheet.
React with Vite vs Next.js: Practical Comparison
| Requirement | React + Vite | Next.js |
|---|---|---|
| Client-heavy SPA | Excellent fit | Can do it, but may be more framework than needed |
| Internal admin dashboard | Often my simpler choice | Useful if server features are required |
| Public SEO-focused pages | Requires additional architecture | Strong fit |
| Server Components | Not provided by Vite alone | Built into App Router architecture |
| Backend endpoints | Separate backend normally required | Route Handlers available |
| Database access from page code | Normally through an API | Possible from Server Components |
| Static hosting | Very straightforward | Possible with static export where suitable |
| Server rendering | Requires additional solution/framework | Built into framework |
| Routing | Add router/framework yourself | Built in |
| Maximum architectural freedom | Higher | More opinionated |
| Initial concepts to learn | Fewer | More |
How I Would Choose for Common Projects
Internal Admin Dashboard
My starting point: React + Vite.
If users must log in before seeing anything and the application already talks to a separate API, server rendering and public SEO may provide very little value.
Public SaaS Website + Application
My starting point: Next.js.
The public marketing pages, authentication flow and application can live in one React architecture while using server rendering where it provides value.
E-commerce Store
My starting point: Next.js.
Product and category pages benefit from server/static rendering strategies, while cart and checkout interfaces can remain highly interactive Client Components.
Large Application with an Existing Node.js Backend
It depends.
If the web frontend needs server rendering, Next.js can sit in front of the existing backend.
If it is an authenticated application with no meaningful server-rendering requirement, React + Vite may stay simpler.
Simple Marketing Website
I would first ask whether React is necessary at all.
Not every five-page website needs a full React application.
The right architectural decision occasionally involves using less JavaScript, not choosing between two ways of using more of it.
When Next.js Can Be Too Much
I like what Next.js gives me, but I don’t think every React project automatically becomes better after moving to Next.js.
Next.js introduces concepts that a simple client application may not need:
- Server and Client Component boundaries
- server rendering
- caching and revalidation behaviour
- server/client serialization boundaries
- Route Handlers
- Server Functions
- framework-specific deployment behaviour
If my application gets no value from those capabilities, I don’t want to introduce them just because Next.js is popular.
When Plain React Starts Becoming Its Own Framework
The opposite problem happens too.
A project can start as “just React” and slowly add:
React
+
React Router
+
custom data fetching
+
SSR solution
+
metadata solution
+
code splitting rules
+
authentication
+
server endpoints
+
custom caching
At some point, the team is effectively maintaining its own framework.
This is also why React’s current documentation recommends considering a framework for new production applications that need routing and other application-level features.
The more framework-like problems I find myself solving manually, the stronger the argument becomes for starting with a framework.
Performance Depends More on Architecture Than the Logo
I wouldn’t choose Next.js because someone says “Next.js is faster than React.”
That statement isn’t useful without knowing what the application is doing.
A badly designed Next.js application can send too much JavaScript, create unnecessary server work, build data waterfalls and make poor caching decisions.
A well-built client-side React dashboard can feel extremely fast because static assets are cached and the application’s API calls are designed well.
I would rather ask:
- How much JavaScript reaches the browser?
- When does the user see useful content?
- Are data requests happening in parallel?
- Can expensive work stay on the server?
- Is static content being regenerated unnecessarily?
- Are client components larger than they need to be?
Those questions produce better performance decisions than choosing a framework based on a benchmark screenshot.
My React vs Next.js Decision Checklist
Before choosing, these are the questions I would answer.
- Is the application public or mostly behind authentication?
- Does search visibility matter?
- Do I already have a backend API?
- Does the page need server-side data before it renders?
- Would Server Components reduce meaningful client-side JavaScript?
- Do I need static, server-rendered and dynamic routes together?
- Do I need backend endpoints inside the web project?
- How important is a simple static deployment?
- Does the team understand server/client boundaries?
- Am I adding enough libraries to plain React that I’m effectively building a framework myself?
The answers usually make the choice much clearer.
React vs Next.js: My Practical Rule
If I’m building a highly interactive client application against an existing backend and I don’t need server rendering, I am happy to keep the frontend simple with React and Vite.
If I’m building a public web application where routing, server-side data, rendering strategy, metadata and backend behaviour are all part of the same product, I would usually start by evaluating Next.js.
That doesn’t make Next.js “better React.”
It means the project needs more than a UI library.
Final Thoughts
The old React vs Next.js comparison used to be fairly easy to explain:
React = client-side SPA
Next.js = React + SSR
I don’t think that model is useful anymore.
Modern React includes an architecture around Server Components, streaming, Suspense and Server Functions that frameworks can integrate deeply.
At the same time, a simple React application built with a tool such as Vite remains a very practical choice when the browser is genuinely where most of the application belongs.
So I don’t begin with:
Which one is more popular?
I begin with:
Where should this application’s work happen?
If most of it belongs in the browser, React with Vite may be enough.
If the application benefits from combining browser interactivity with server rendering, server-side data access, routing and full-stack features, Next.js becomes much more compelling.
The framework should follow the architecture. I wouldn’t design the architecture around the framework.
Continue Learning
If you’re working with React or TypeScript applications, these guides continue from the same practical approach:




