All articles
9 min read
October 23, 20269 min read

Next.js 15 App Router Deep Dive: Server Components, Streaming SSR & Partial Prerendering

Master Next.js 15 App Router internals: React Server Components (RSC) boundary rules, streaming SSR with Suspense, Parallel Routes, and Partial Prerendering (PPR).

Tags:#Next.js#React#Server Components#Streaming SSR#Web Development
Next.js 15 App Router Deep Dive: Server Components, Streaming SSR & Partial Prerendering

Next.js 15 marks a foundational shift in how full-stack React web applications are built, rendered, and delivered. The transition from legacy Pages router (getServerSideProps) to the App Router represents a paradigm leap into React Server Components (RSC), streaming SSR, and Partial Prerendering (PPR).

In this deep dive, we break down how Next.js 15 delivers zero-bundle-size server components and ultra-responsive user experiences.

Next.js Architecture
Next.js Architecture

Server components execute exclusively on the node/edge runtime, sending only streaming HTML and RSC payloads.

RSC vs Client Component Execution Model

CharacteristicReact Server Component (Default)Client Component ("use client")
Execution EnvironmentServer-only (Node.js / Edge)Server SSR + Client Hydration
JavaScript Bundle Size0 KB sent to browserShipped in client JS bundle
Access to DB & SecretsDirect access (pg, sql, API tokens)Strictly forbidden (exposed to browser)
Interactivity (onClick, useState)Not supportedFully supported
Data FetchingDirect async/await in componentReact 19 use(), SWR, or useEffect

Streaming SSR with Suspense Boundaries

In traditional SSR, the server must wait for all database queries to complete before sending a single byte of HTML. If one slow microservice takes 1.5s, the user stares at a white blank screen for 1.5s.

Streaming SSR solves this by flushing the initial static HTML shell instantly, streaming dynamic chunks as asynchronous promises resolve:

tsx
import { Suspense } from "react";
import { getProfile, getProjects } from "@/lib/api";

// Fast Server Component
export default async function DashboardPage() {
  const profile = await getProfile();

  return (
    <div className="max-w-5xl mx-auto py-12 px-6">
      <header className="mb-8">
        <h1 className="text-3xl font-bold">Welcome back, {profile.full_name}</h1>
      </header>

      {/* Stream the slow analytics component without delaying header render */}
      <Suspense fallback={<AnalyticsSkeleton />}>
        <SlowAnalyticsWidget />
      </Suspense>
    </div>
  );
}

// Slow async component wrapped in Suspense
async function SlowAnalyticsWidget() {
  const projects = await getProjects(); // Takes 800ms
  return (
    <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
      {projects.map((p) => (
        <div key={p.id} className="p-6 rounded-xl border border-line bg-card">
          <h3 className="font-semibold">{p.name}</h3>
          <p className="text-sm text-muted">{p.category}</p>
        </div>
      ))}
    </div>
  );
}

function AnalyticsSkeleton() {
  return <div className="h-48 rounded-xl bg-ink/[0.04] animate-pulse" />;
}

What is Partial Prerendering (PPR)?

Next.js 15 introduces Partial Prerendering (PPR), bridging static site generation and dynamic streaming:

  1. Static Shell: At build time, Next.js prerenders the static layout and skeleton frames into pure static HTML hosted on the Edge CDN.
  2. Dynamic Holes: When a user visits the URL, the CDN serves the static shell in 10ms, and the serverless runtime concurrently streams dynamic content into the Suspense boundaries over the same HTTP stream.
Production Recommendation: Place "use client" boundaries as deep down the component tree as possible (e.g., on individual buttons or inputs) so parent layouts remain 100% server components with zero client JavaScript overhead.
Next.js 15 App Router Deep Dive: Server Components, Streaming SSR & Partial Prerendering | Md Jihanur Rahman