React Server Components explained for teams shipping real products
Server components are not 'SSR with extra steps'. They change what code ships to the browser at all — and that changes how you structure a codebase.
Build new Next.js projects on the App Router: it is where server components, streaming, layouts and the current caching model live, and it is the router receiving new features. Keep an existing Pages Router app where it is unless you need those capabilities — the two routers can run side by side in the same project, so migration can be incremental rather than a rewrite.
The Pages Router renders React on the client with server-prepared props; the App Router renders React on the server by default and ships client JavaScript only where you ask for it.
In the Pages Router, every component in your tree is a client component. The server's job is to run getServerSideProps or getStaticProps, serialise the result into JSON, and hand it to React on the client for hydration. That model is predictable and easy to reason about, but it means the entire page component tree ships to the browser.
In the App Router, components are React Server Components unless you add the "use client" directive. Server components run only on the server, never ship their code to the browser, and can await data directly inside the component. Client components still exist — you need them for state, effects and event handlers — but they become leaves in the tree rather than the whole tree.
| Concern | Pages Router | App Router |
|---|---|---|
| Default component type | Client component | Server component |
| Data fetching | getServerSideProps / getStaticProps | await inside the component |
| Shared UI | _app.js and per-page wrappers | Nested layout.tsx files |
| Loading UI | Manual state flags | loading.tsx and Suspense streaming |
| Error handling | Custom _error page | error.tsx per route segment |
| Metadata | next/head | Metadata API / generateMetadata |
Stay on the Pages Router when the app is stable, the team is productive, and nothing you need is App-Router-only.
Migration is not free. It costs review cycles, regression risk and a period where two mental models live in one codebase. If your application ships features on schedule and its performance profile is acceptable, the correct engineering decision is often to leave it alone.
In our client migrations the file moves are the easy part. What consistently causes friction is code that assumed it was running in the browser: direct window access at module scope, context providers wrapping the entire app, and data libraries configured with client-only singletons.
// app/blog/[slug]/page.tsx — params is a Promise in current Next.js
export default async function Page({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await getPost(slug); // runs on the server only
return <Article post={post} />;
}The headline win is bundle size. Moving data formatting, date libraries, markdown parsing and business logic into server components removes them from the client bundle entirely. On one client dashboard we cut first-load JavaScript from 412 KB to 178 KB purely by relocating logic, with no feature changes.
The second win is streaming. With loading.tsx and Suspense boundaries, the shell of the page reaches the browser while slow data is still resolving. That improves perceived performance and Largest Contentful Paint even when total response time is unchanged.
The risk is the inverse: a waterfall of sequential awaits in nested server components can be slower than one parallel client fetch. Fetch in parallel with Promise.all where requests are independent, and measure before assuming the server path is faster.
“Migrate a route when the migration pays for itself on that route. Not because the docs changed.”
— ROVQIX engineering principle
No. The Pages Router is still supported and still receives maintenance, but new rendering features are built for the App Router. Treat it as stable and frozen rather than dying.
Yes. Next.js supports both directories in one application and resolves App Router routes first when paths collide. This is the recommended way to migrate incrementally.
For a 20–40 route application with normal complexity, plan two to four engineering weeks including QA. The variable is not route count but how much client-only logic is tangled into shared layouts.
Indirectly. Server-rendered HTML, the Metadata API and streaming all help crawlability and Core Web Vitals, but a well-built Pages Router site with SSR is also fully indexable. The router is not itself a ranking factor.
Harshal Patel
Founder & Lead Engineer, ROVQIX
Harshal leads engineering at ROVQIX, where he has shipped production Next.js, Node.js and PostgreSQL systems for startups, SaaS teams and ecommerce brands. He writes about the trade-offs behind architecture decisions rather than the framework of the week.
ROVQIXdesigns and builds production web platforms — Next.js front ends, Node.js APIs and the infrastructure behind them. Tell us what you're building and we'll scope it with you.
Server components are not 'SSR with extra steps'. They change what code ships to the browser at all — and that changes how you structure a codebase.
Rendering strategy is a per-route decision, not an architectural religion. A single Next.js app can and should use all four.
Most 'Next.js is showing stale data' bugs are one of four caches doing exactly what it was told. Here is the mental model that makes them predictable.
No spam. Just the occasional case study and craft breakdown.