App Router replaces Pages Router's page-level SSR (getServerSideProps) with component-level SSR using Server Components, shifting from per-request data fetching to granular, streaming server rendering with selective hydration
The evolution from Pages Router to App Router in Next.js represents a fundamental shift in how Server-Side Rendering (SSR) is approached. Pages Router treats SSR as a page-level concern using getServerSideProps, while App Router integrates SSR at the component level through React Server Components (RSC). This change moves from a 'fetch-on-render' model to a 'render-with-fetch' model, where components can directly access server resources and stream their output progressively to the client, dramatically reducing client-side JavaScript and improving performance.
Pages Router SSR: Page-level, monolithic rendering with getServerSideProps running on every request, fetching all data before rendering begins, and sending complete HTML to client
App Router SSR: Component-level, granular rendering with Server Components that can fetch data directly and stream progressively, allowing dynamic and static content to coexist in the same route
Data fetching location: Pages Router requires data fetching in special functions outside components; App Router allows direct data fetching inside components using async/await
Hydration approach: Pages Router hydrates entire pages; App Router selectively hydrates only Client Components, leaving Server Components as pure server output
React Server Components (RSC) fundamentally redefine the SSR model by introducing a new rendering target. Instead of treating all components as potentially interactive, RSC establishes a default of server-only execution. Components run exclusively on the server, generate a special RSC Payload (a compact binary format containing rendered output and client component references), and never ship JavaScript to the client . This eliminates the traditional trade-off where SSR meant shipping the same component code twice—once for server HTML and again for client hydration. With RSC, only components explicitly marked with 'use client' become part of the client bundle, while the rest remain pure server-rendered output .
Zero bundle size for server components: Dependencies like date libraries, Markdown parsers, or database drivers used in Server Components never reach the client, dramatically reducing JavaScript payload
Direct server access: Server Components can directly query databases, read files, or access internal APIs without creating separate API endpoints, eliminating the need for data-fetching layers
Automatic code splitting: Server Components enable route-based automatic code splitting without manual React.lazy calls, as client components are naturally split at the server/client boundary
Streaming with Suspense: Server Components work natively with React Suspense, allowing parts of the UI to stream as they become ready, improving perceived performance
When a Server Component renders, it produces an RSC Payload—a compact binary representation of the component tree. This payload contains the rendered HTML of Server Components, props to be passed to Client Components, and placeholder references where Client Components will be mounted. Unlike traditional SSR which sends HTML only, the RSC Payload maintains the component tree structure and enables seamless integration between server and client components. The client receives this payload and uses it to reconstruct the component tree, hydrating only the Client Component boundaries . This is why navigation in App Router feels instant even with Server Components—the RSC Payload can be fetched and rendered without full page reloads.
Immediate shell delivery: The server sends the static shell (layout and fallback UI) immediately, improving Time to First Byte (TTFB)
Progressive content streaming: As Server Components resolve their data, their output streams to the client in chunks, visible as they arrive
Selective hydration: Only Client Components that are actually interactive (or wrapped in Suspense) hydrate, reducing main thread work
Parallel data resolution: Multiple Suspense boundaries can resolve their data in parallel, each streaming independently when ready
The 'use client' directive creates a clear separation between server and client execution. Everything above the boundary (parent components) runs on the server; everything below (the component itself and its children) becomes client-rendered and hydrated. This boundary is where the RSC Payload includes references to JavaScript bundles. Crucially, client components can still render server components as children through composition, but the boundary itself marks where serialization happens—props passed from server to client must be serializable (no functions, dates, or circular references) . This forces developers to think deliberately about what belongs on the client versus server.
Mental model shift: Components are server-first by default; interactivity is opt-in via 'use client'
Data fetching consolidation: Instead of separate API routes and client-side fetching, Server Components can directly access databases
Performance optimization: Bundle size naturally optimized as server dependencies are excluded; no need for manual code splitting
Progressive enhancement: Pages work without JavaScript initially, then enhance with client components
Testing complexity: Server Components require different testing strategies as they run in Node.js environment, not browser
Next.js is evolving toward Partial Prerendering (PPR), which combines static and dynamic rendering within the same route. With PPR, a static shell is prerendered at build time, while dynamic components stream in at request time. In Next.js 16, PPR concepts are being integrated into a new 'Cache Component' model, further blurring the line between static and dynamic rendering. This represents the logical conclusion of the RSC paradigm: components declare their data dependencies and caching needs, and the framework determines at runtime whether to serve cached static content or generate fresh dynamic content . The future of SSR in Next.js is increasingly declarative rather than imperative.