404 Not Found

404 Not Found


nginx

Server Components Mental Model: The React 19 Server-Side Rendering Revolution

The RSC mental model is the most important paradigm shift in Next.js 16—only by understanding it can you truly grasp the design philosophy behind the App Router.

1. What You'll Learn


2. A True Story of a Full-Stack Developer

(1) Pain Point: Bundle Size Is Out of Control

Alice is a full-stack developer on the TaskFlow team. The Dashboard page she built includes a data table component that references three libraries—date-fns, recharts, and lodash—simply to format dates and draw a few bar charts. The page’s initial JS bundle ballooned to 480 KB, and its Lighthouse Performance score dropped to 52. To make matters worse, this table is merely a server-rendered, purely presentational component—users don’t need to interact with it at all—yet the code from these libraries is still downloaded to the client.

(2) Solution to the RSC

RSC ensures that server-side components run only on the server, outputting pure HTML and serialized data, with JavaScript bundles completely excluded.

TSX
// app/dashboard/page.tsx — Server Component(Zero-Client JS)
import { getSalesData } from '@/lib/db'
import { formatDistanceToNow } from 'date-fns'

export default async function DashboardPage() {
  const sales = await getSalesData()  // Direct Access to the Database
  return (
    <div>
      <h1>Dashboard — {sales.length} records</h1>
      <SalesTable data={sales} />
    </div>
  )
}

async function SalesTable({ data }: { data: Sale[] }) {
  return (
    <table>
      {data.map(row => (
        <tr key={row.id}>
          <td>{formatDistanceToNow(row.createdAt)}</td>
          <td>{row.amount}</td>
        </tr>
      ))}
    </table>
  )
}

(3) Revenue

Dimension Pure Client Component RSC
Bundle Size 480 KB (includes date-fns + ReCharts + Lodash) 0 KB (server-side libraries are not downloaded)
Database Access Requires API Route Relay Direct Access (Zero Latency)
Above-the-fold rendering Requires JS download + execution Instant HTML
SEO Relies on SSR / client-side rendering Native support
Lighthouse Score 52 96

3. Basic Definition of RSC

RSC (React Server Component) is a new component type introduced in React 19. It runs only on the server and is never sent to the client’s browser. RSC code (including dependent libraries) does not appear in the JS bundle, so you can safely use large libraries, access databases directly, and read the file system.

100%
graph TB
    subgraph "Server-side (Server)"
        A[RSC Components] --> B[Database/File System/API]
        A --> C[Serialize to RSC Payload<br/>React Flight Agreement]
    end
    subgraph "Client (Browser)"
        D[RSC Payload] --> E[Client Component<br/>Preserve the interaction logic]
        D --> F[Pure HTML Rendering<br/>Zero JS Overhead]
    end
    C --> D
    style A fill:#d4edda
    style D fill:#cce5ff
Characteristic Server Component Client Component
Runtime Server-side (Node.js) Browser
JS Bundle ❌ Not included ✅ Included
Database/File System ✅ Direct Access ❌ Not Available (API Required)
React Hooks (useState/useEffect) ❌ Not available ✅ Available
Event Handling (onClick/onSubmit) ❌ Not available ✅ Available
Async/Await ✅ Native support ❌ Requires additional handling

(1) The Meaning of "Zero-Client JS"

The core principle of RSC is: If a component has no interactive logic, its code should not be sent to the browser. This means:

(2) RSC vs. Traditional SSR

Traditional SSR (Page Router) also renders HTML on the server, but it still sends the component’s JavaScript code to the client for hydration. RSC, on the other hand, is completely different—the code for server-side components never reaches the client.

Dimension Traditional SSR (Pages Router) RSC (App Router)
Server-side rendering ✅ HTML ✅ HTML
Client Hydration ✅ Full ❌ Not required
Component code sent to the client ✅ All ❌ Client Component only
State-preserving Requires careful handling Naturally stateless
Data Retrieval Timing getServerSideProps Directly within the component await

▶ Example: Verifying Bundle Size Differences (Difficulty: ⭐⭐)

TSX
// app/bundle-demo/page.tsx — Server Component(Purely server-side)
import { format, addDays } from 'date-fns'

export default function BundleDemoPage() {
  const today = new Date()
  const dates = Array.from({ length: 7 }, (_, i) => {
    const d = addDays(today, i)
    return { label: format(d, 'EEEE'), date: format(d, 'yyyy-MM-dd') }
  })

  return (
    <div>
      <h1>This Week</h1>
      <ul>{dates.map(d => <li key={d.date}>{d.label}: {d.date}</li>)}</ul>
    </div>
  )
}

Output:

TEXT
Post-Build Checks .next/static/chunks — Excludes date-fns the code

4. The 'use client' Directive and Client Boundaries

'use client' is a module-level directive that marks a component in a file as a Client Component. When interactive functionality (useState, onClick, useEffect) is required in the RSC component tree, this directive must be added at the top of the file.

100%
graph TB
    A[Root Layout<br/>Server Component] --> B[NavBar<br/>Server Component]
    A --> C[DashboardPage<br/>Server Component]
    C --> D[SalesChart<br/>'use client']
    C --> E[DataTable<br/>Server Component]
    D --> F[Interaction Logic<br/>useState / useEffect]

    style A fill:#d4edda
    style B fill:#d4edda
    style C fill:#d4edda
    style D fill:#cce5ff
    style E fill:#d4edda
Command Function Example
'use client' Mark the module as a Client Component 'use client'; export default function Btn() { ... }
'use server' Mark function as Server Action 'use server'; export async function create() { ... }

(1) Boundary Penetration Rules

There are two ironclad rules in the RSC component tree:

TSX
// ✅ Correct:Server Component Import Client Component
// app/page.tsx (Server)
import ClientCounter from './ClientCounter'
export default function Page() {
  return <ClientCounter />
}

// app/ClientCounter.tsx (Client)
'use client'
import { useState } from 'react'
export default function ClientCounter() {
  const [count, setCount] = useState(0)
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>
}
TSX
// ❌ Error:Client Component Cannot import directly Server Component
// app/ClientList.tsx
'use client'
import ServerItem from './ServerItem'  // ❌ Compilation Error:Server Component Cannot be imported on the client side

export default function ClientList() {
  return <ServerItem />  // This line will cause an error.
}

(2) How to Embed a Server Component in a Client Component

Tip: Pass data via children props—the children slot in the client component can receive the rendering result from the server component.

TSX
// app/layout.tsx (Server)
import ClientShell from './ClientShell'
import ServerSidebar from './ServerSidebar'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <ClientShell sidebar={<ServerSidebar />}>
      {children}
    </ClientShell>
  )
}

▶ Example: The Correct Way to Handle Prop Propagation — Children Props (Difficulty ⭐⭐)

TSX
// app/interleaving/ClientWrapper.tsx
'use client'
import { useState } from 'react'

export default function ClientWrapper({ sidebar, children }: {
  sidebar: React.ReactNode
  children: React.ReactNode
}) {
  const [isOpen, setIsOpen] = useState(true)
  return (
    <div style={{ display: 'flex' }}>
      {isOpen && <aside>{sidebar}</aside>}
      <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
      <main>{children}</main>
    </div>
  )
}
TSX
// app/interleaving/page.tsx (Server)
import ClientWrapper from './ClientWrapper'
import { getSidebarData } from '@/lib/db'

export default function InterleavingPage() {
  const items = getSidebarData() // Retrieving Data on the Server Side
  return (
    <ClientWrapper sidebar={<ServerItemList items={items} />}>
      <h1>Main Content</h1>
    </ClientWrapper>
  )
}

async function ServerItemList({ items }: { items: string[] }) {
  return <ul>{items.map(i => <li key={i}>{i}</li>)}</ul>
}

▶ Example: Serializable Props Restrictions (Difficulty: ⭐⭐⭐)

TSX
// app/serializable/page.tsx (Server)
function greet() { return 'hello' }  // ❌ Functions are not serializable
const date = new Date()              // ⚠️ Date Not Permissible RSC Props

// app/serializable/ClientComponent.tsx
'use client'
export default function ClientComponent(props: {
  fn: () => string       // ❌ Functions as props → Runtime Error
  date: Date             // ⚠️ Date → Converted to string Time zone may be missing
  data: { name: string } // ✅ Ordinary objects can
}) {
  return <div>{props.data.name}</div>
}

5. Merging the React Flight Payload with the Component Tree

After the RSC server-side rendering is complete, it outputs a special data format called the RSC Payload (React Flight Protocol), which contains the serialized HTML tree, component references, and props data. When the client receives the payload, it merges it with the local client component to form the final component tree.

(1) Structure of the RSC Payload

100%
sequenceDiagram
    participant Server as Next.js Server
    participant Client as Browser

    Server->>Server: Execute RSC Component Tree
    Server->>Server: Serialize to React Flight Payload
    Server->>Client: Send RSC Payload + HTML
    Client->>Client: Analysis Flight Payload
    Client->>Client: Merge Client Component(Hydrate)
    Client->>Client: Render the final interface
Component Description Example
Server Component Output Serialized HTML fragment <div><h1>Dashboard</h1></div>
Client Component Reference Module ID + Props {id: "./chart.js", props: {data: [...]}}
Data Reference Database Query Results {sales: [{id:1, amount: 100}]}
Stream Suspense Boundary Splitting Sending Multiple Chunks Incrementally

▶ Example: Viewing the RSC Payload (Difficulty: ⭐⭐)

View the RSC response in the Network panel of your browser's developer tools:

BASH
# Open Network Panel,Refresh the page,Filter Fetch/XHR
# Find the request for the current page,View Response
# Content-Type: text/x-component That is to say RSC Payload

Output:

TEXT
# RSC Payload Excerpt(Simplify):
M1:{"id":"./app/page.tsx","chunks":["app/page-abc123.js"]}
J0:["$","div",null,{"children":["$","h1",null,{"children":"Dashboard"}]}]
S1:"react.suspense"

▶ Example: How a Client Component References a Server Component (Difficulty: ⭐⭐⭐)

TSX
// app/flight-demo/ServerData.tsx — Pure server-side data components
export default async function ServerData() {
  const data = await fetch('https://api.example.com/data').then(r => r.json())
  return <pre>{JSON.stringify(data, null, 2)}</pre>
}
TSX
// app/flight-demo/ClientShell.tsx
'use client'
export default function ClientShell({ dataSlot }: { dataSlot: React.ReactNode }) {
  return (
    <div className="card">
      <h2>Client Shell</h2>
      <div className="server-data">{dataSlot}</div>
    </div>
  )
}
TSX
// app/flight-demo/page.tsx
import ClientShell from './ClientShell'
import ServerData from './ServerData'

export default function FlightDemoPage() {
  return (
    <ClientShell dataSlot={<ServerData />}>
  )
}

6. Complete Example: RSC Component Tree Architecture

TSX
// app/rsc-architecture/layout.tsx — RSC Layout
import ClientShell from './ClientShell'
import { getUser } from '@/lib/auth'

export default async function RscLayout({ children }: { children: React.ReactNode }) {
  const user = await getUser()                    // ✅ Direct Database Queries
  return (
    <ClientShell username={user?.name ?? 'Guest'}>
      <nav>
        <a href="/">Home</a>
        <a href="/dashboard">Dashboard</a>
        <a href="/settings">Settings</a>
      </nav>
      {children}
    </ClientShell>
  )
}

// app/rsc-architecture/ClientShell.tsx
'use client'
import { useState } from 'react'
import type { ReactNode } from 'react'

export default function ClientShell({ username, children }: {
  username: string
  children: ReactNode
}) {
  const [theme, setTheme] = useState<'light' | 'dark'>('light')
  return (
    <div data-theme={theme}>
      <header>
        <span>Welcome, {username}</span>
        <button onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')}>
          Toggle {theme}
        </button>
      </header>
      {children}
    </div>
  )
}

// app/rsc-architecture/page.tsx
import { getProjects } from '@/lib/db'

export default async function RscArchitecturePage() {
  const projects = await getProjects()
  return (
    <div>
      <h1>Projects ({projects.length})</h1>
      <table>
        <thead><tr><th>Name</th><th>Status</th></tr></thead>
        <tbody>
          {projects.map(p => (
            <tr key={p.id}>
              <td>{p.name}</td>
              <td><StatusBadge status={p.status} /></td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  )
}

function StatusBadge({ status }: { status: string }) {
  const colors: Record<string, string> = {
    active: '#4caf50', archived: '#9e9e9e', draft: '#ff9800'
  }
  return <span style={{ background: colors[status] ?? '#ccc', padding: '2px 8px', borderRadius: 4 }}>{status}</span>
}

❓ FAQ

Q Are RSC and SSR the same thing?
A No. With traditional SSR, JavaScript code is still sent to the client after the HTML is rendered on the server (hydration). With RSC, the Server Component code is never sent to the client—zero JavaScript overhead. SSR and RSC can coexist (the default mode for the App Router is a combination of RSC and SSR).
Q Are all components in a 'use client' file client components?
A Yes. 'use client' is a module-level directive—all components exported from a file are client components. It is recommended to split interactive components into separate files to reduce the amount of client-side code.
Q Why can't a Client Component directly import a Server Component?
A Because Server Components only exist in the server-side runtime. When a Client Component runs in the browser, the Server Component's code doesn't exist at all. The correct approach is to bridge the two using the children prop or Server Actions.
Q What happens if a function is passed as a prop to a client component?
A An error will be thrown. The RSC payload is based on JSON serialization (the React Flight protocol), and functions cannot be serialized. If you need to pass a callback, you should use Server Actions or the event handler pattern.
Q How do you determine whether a component should be a Server or a Client?
A The simplest rule of thumb: if the component requires interactivity (useState, useEffect, onClick, browser APIs), it’s a Client; otherwise, use a Server Component by default. A common optimization strategy is to extract the interactive parts into a small Client wrapper, while keeping the main body as a Server Component.
Q What is the relationship between the RSC payload and HTML?
A Next.js 16 sends both HTML (for Instant Loading) and the RSC payload (for component tree reconstruction). The HTML ensures the first screen is displayed immediately, while the RSC payload takes over interactions after being parsed on the client. Together, they deliver "instant first screen + full interactivity."

📖 Summary


📝 Exercises

  1. Basic Exercise (⭐): Create a Server Component in app/page.tsx that directly calls fetch('https://api.github.com/repos/vercel/next.js') to retrieve data and render the star rating. Verify that the client does not download libraries such as node-fetch.

  2. Advanced Exercise (⭐⭐): Create a page containing a Client Component (counter button) and a Server Component (user list), and pass the server data to the client wrapper using the children prop. Verify the RSC Payload response in the browser’s DevTools Network tab.

  3. Challenge (⭐⭐⭐): Build a three-tier component tree: Layout (Server) → ClientTabs (Client, containing useState to manage the current tab) → ServerTabContent (Server Components passed via children), where each tab’s content consists of a different asynchronous data query. Ensure that fetching data from all Server Components does not increase the client-side bundle size.

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏