How React SSR Differs from React Server Components (RSC) in Next.js
React SSR generates full HTML strings on every request that are hydrated by the client, while React Server Components execute exclusively on the server to stream a serialized RSC payload, enabling smaller client bundles and progressive hydration without sending server-only code to the browser.
Next.js supports two distinct server-rendering architectures that solve different performance and developer experience challenges. While both React SSR and React Server Components (RSC) execute on the server, the vercel/next.js codebase implements them through entirely separate pipelines with different output formats, bundle semantics, and data-fetching patterns.
Architectural Differences: HTML Strings vs. Serialized Payloads
Execution Model and Output Format
With traditional React SSR, the code runs on every request to produce a complete HTML string via renderToHTML (implemented in packages/next/src/server/base-server.ts). The server sends this final markup to the browser, and the client hydrates it into an interactive application.
In contrast, React Server Components run only on the server to generate an RSCPayload — a special JSON-like format created by generateDynamicRSCPayload in packages/next/src/server/app-render/app-render.tsx. The client-side React runtime consumes this streamed payload and reconstructs the UI without requiring the component code itself.
What the Client Receives
SSR responses include pure HTML plus a JavaScript bundle containing all imports, including data-fetching libraries. This means code used exclusively for server-side logic still ends up in the client bundle.
RSC responses send an HTML shell instantly, followed by the RSC stream (detected via the RSC_HEADER defined in packages/next/src/server/app-render/app-render.tsx). The React Flight loader in packages/next/src/build/webpack/plugins/next-flight-plugin.ts strips server-only imports from client bundles, ensuring .server.tsx files never reach the browser.
Data Fetching and Bundle Size Implications
Traditional SSR Data Fetching
SSR requires explicit data fetching through export async function getServerSideProps(context). This function runs per request in packages/next/src/server/route-modules/pages/module.ts, which merges the returned props into the HTML response. Any modules imported for data fetching are included in the client JavaScript.
Direct Data Access in RSC
Inside .server.tsx files, you can await data directly in the component body using server-only modules like node-fetch. The data becomes part of the serialized RSCPayload without explicit prop drilling. Because the component executes server-side and only the result streams to the client, heavy logic and dependencies remain entirely on the server, drastically reducing bundle size.
Streaming, Caching, and Error Resilience
SSR through renderToHTMLImpl can stream HTML, but the HTML must be fully generated before the response is considered complete. Errors thrown in getServerSideProps or during rendering abort the entire page generation, typically resulting in a generic error page.
RSC is inherently streaming via getFlightStream. The HTML shell arrives first, followed by partial RRC slices as they become available. Errors are isolated to specific components; app-render.tsx embeds error placeholders in the payload while the rest of the page continues rendering, enabling fine-grained error boundaries and progressive hydration.
Implementation in the Next.js Source Code
The SSR Pipeline
The core SSR implementation resides in packages/next/src/server/route-modules/pages/module.ts, which orchestrates the call to BaseServer.renderToHTMLImpl in packages/next/src/server/base-server.ts. This pipeline handles getServerSideProps parsing, HTML generation, and the merging of server-side data into the final markup.
The RSC Pipeline
The RSC entry point is packages/next/src/server/app-render/app-render.tsx, which detects the RSC header and builds the payload via generateDynamicRSCPayload. The streaming response is managed by use-flight-response.tsx (referenced internally by the app renderer). Server-only component detection happens at build time through packages/next/src/build/webpack/plugins/next-flight-plugin.ts, which ensures incompatible imports are excluded from client bundles.
Code Examples
Traditional SSR with getServerSideProps
// pages/profile.tsx
import type { GetServerSideProps } from 'next'
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const res = await fetch(`https://api.example.com/user/${ctx.params?.id}`)
const user = await res.json()
return { props: { user } }
}
export default function Profile({ user }: { user: any }) {
return (
<div>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</div>
)
}
getServerSideProps executes in packages/next/src/server/route-modules/pages/module.ts on every request, and the resulting props are serialized into the HTML generated by renderToHTML.
React Server Component with Server-Only Imports
// app/dashboard/profile.server.tsx
import fetch from 'node-fetch' // ✅ server-only import
export default async function Profile() {
const res = await fetch('https://api.example.com/user/me')
const user = await res.json()
return (
<section>
<h1>{user.name}</h1>
<p>{user.bio}</p>
</section>
)
}
The component runs only on the server. Next.js streams the resulting RSC payload from generateDynamicRSCPayload in app-render.tsx, and the client receives a lightweight HTML shell that hydrates when the payload arrives.
Combining SSR and RSC
// app/dashboard/page.tsx
import Profile from './profile.server' // RSC
import { getServerSideProps } from '../../pages/api/preview' // traditional SSR hook
export const getServerSideProps = async (ctx) => {
// Example: set a preview cookie for the whole page
return { props: {} }
}
export default function Dashboard() {
return (
<main>
<h2>Dashboard (SSR page)</h2>
{/* RSC will stream separately */}
<Profile />
</main>
)
}
This demonstrates incremental adoption: the outer page renders via renderToHTML (SSR), while the <Profile /> component streams as an isolated RSC payload.
Summary
- React SSR produces complete HTML strings on each request via
renderToHTML, suitable for SEO-critical pages but requiring full hydration and sending all imports to the client. - React Server Components stream a serialized
RSCPayloadviagenerateDynamicRSCPayload, enabling progressive hydration and zero client JavaScript for server-only logic. - SSR bundles all imported modules for the client; RSC strips server-only code through the Flight plugin (
next-flight-plugin.ts). - Data fetching in SSR requires
getServerSidePropsinpages/module.ts; RSC allows directawaitinside async components executing inapp-render.tsx. - RSC provides granular error boundaries and streaming by default, while SSR errors in
base-server.tsfail the entire page render.
Frequently Asked Questions
Can I use both SSR and RSC in the same Next.js application?
Yes. Next.js supports incremental adoption, allowing you to mix traditional pages directory SSR with App Router RSCs. You can embed RSC payloads within SSR pages, as the RSC_HEADER negotiation in packages/next/src/server/app-render/app-render.tsx handles the streaming separately from the HTML generation in packages/next/src/server/base-server.ts.
Why does RSC reduce client bundle size compared to SSR?
In SSR, all modules imported by a page are bundled for the client even if only used for data fetching. RSC uses the Flight loader defined in packages/next/src/build/webpack/plugins/next-flight-plugin.ts to ensure .server.tsx files and their dependencies are excluded from client bundles, sending only the serialized result.
How does error handling differ between SSR and RSC in production?
SSR errors in getServerSideProps or during renderToHTML abort the entire response, typically showing a generic error page. RSC errors are captured at the component level in app-render.tsx, allowing the server to embed error placeholders in the payload while streaming continues for other components.
Is getServerSideProps deprecated in favor of React Server Components?
No. While RSC represents the future architecture for the App Router, getServerSideProps remains fully supported in the Pages Router for request-time data fetching that produces complete HTML documents. The Next.js source code maintains both renderToHTML in base-server.ts and the RSC pipeline in app-render.tsx as first-class citizens.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →