A complete guide to React Server Components (RSC). Learn the difference between server and client components, streaming, suspense and practical patterns for Next.js applications.
What are React Server Components?¶
React Server Components (RSC) represent a fundamental change in how we think about rendering React applications. Unlike traditional client-side rendering or server-side rendering (SSR), RSC introduces a third paradigm — components that render exclusively on the server and are never transferred to the browser as JavaScript.
The main motivation for creating RSC was to reduce the amount of JavaScript sent to the client. In traditional React applications, the entire component tree including dependencies gets downloaded to the browser, even if many components only display static data. RSC solves this problem elegantly — server components render on the server, and only the resulting HTML with minimal JavaScript for interactivity is sent to the client.
The RSC concept was first introduced by the React team in December 2020, but practical adoption was enabled by Next.js 13 with App Router, which integrated RSC as default behavior. Today, RSC is a key part of the modern React ecosystem and influences how architects design web applications.
RSC Architecture¶
RSC introduces two categories of components: Server Components (default in Next.js App Router) and Client Components (marked with ‘use client’ directive). Server Components have direct access to databases, file system, and other server resources. Client Components have access to browser APIs, useState, useEffect, and other hooks requiring interactivity.
// Server Component (default in Next.js App Router)
// This code is NEVER sent to the browser
import { db } from '@/lib/database'
async function ProductList() {
// Přímý přístup k databázi — žádné API volání
const products = await db.query('SELECT * FROM products WHERE active = true')
return (
<div className="product-grid">
{products.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
)
}
// Client Component — potřebuje interaktivitu
'use client'
import { useState } from 'react'
function AddToCartButton({ productId }) {
const [loading, setLoading] = useState(false)
const handleClick = async () => {
setLoading(true)
await fetch('/api/cart', {
method: 'POST',
body: JSON.stringify({ productId })
})
setLoading(false)
}
return (
<button onClick={handleClick} disabled={loading}>
{loading ? 'Přidávám...' : 'Přidat do košíku'}
</button>
)
}
Streaming a Suspense¶
Jednou z nejsilnějších vlastností RSC je podpora streamování obsahu. Místo čekání na kompletní vykreslení celé stránky může server posílat části UI postupně, jak se data načítají. Tento přístup výrazně zlepšuje vnímanou rychlost aplikace, protože uživatel vidí obsah okamžitě, zatímco pomalejší části se načítají na pozadí.
React Suspense slouží jako mechanismus pro deklarativní definici loading stavů. Když serverová komponenta provádí asynchronní operaci (například dotaz do databáze), Suspense boundary zobrazí fallback obsah, dokud se data nenačtou. Na klientovi se pak plynule nahradí skutečným obsahem bez jakéhokoli blikání.
import { Suspense } from 'react'
// Stránka s postupným načítáním
export default function DashboardPage() {
return (
<div className="dashboard">
{/* Toto se zobrazí okamžitě */}
<h1>Dashboard</h1>
<NavigationBar />
{/* Statistiky se načtou jako první */}
<Suspense fallback={<StatsSkeleton />}>
<DashboardStats />
</Suspense>
{/* Graf se načte nezávisle */}
<Suspense fallback={<ChartSkeleton />}>
<RevenueChart />
</Suspense>
{/* Tabulka s daty se může načíst jako poslední */}
<Suspense fallback={<TableSkeleton />}>
<RecentOrders />
</Suspense>
</div>
)
}
// Každá komponenta si načte data nezávisle
async function DashboardStats() {
const stats = await getStats() // pomalý dotaz
return <StatsGrid data={stats} />
}
Parallel Data Fetching¶
RSC přirozeně podporují paralelní načítání dat. Každá serverová komponenta může nezávisle načítat svá data, a díky Suspense boundaries se tyto požadavky provádějí paralelně. To eliminuje klasický problém vodopádových požadavků (waterfall requests), kde se data načítají sekvenčně.
V praxi to znamená, že pokud máte stránku s profilem uživatele, jeho objednávkami a doporučeními, všechny tři datové zdroje se dotazují současně, místo aby čekaly jeden na druhého. Výsledek je dramatické zrychlení načítání stránek, zejména u aplikací s mnoha nezávislými datovými zdroji.
Composition Patterns¶
Správná kompozice serverových a klientských komponent je klíčová pro efektivní využití RSC. Základní pravidlo říká: server komponenty mohou importovat client komponenty, ale ne naopak. Client komponenty nemohou přímo importovat server komponenty, ale mohou je přijímat jako children props.
// ✅ Správný vzor: Server komponenta předá data client komponentě
// layout.tsx (Server Component)
import { getUser } from '@/lib/auth'
import { InteractiveNav } from '@/components/InteractiveNav'
export default async function Layout({ children }) {
const user = await getUser()
return (
<div>
{/* Client komponenta přijímá serializovatelná data */}
<InteractiveNav userName={user.name} role={user.role} />
{/* Server komponenty jako children */}
{children}
</div>
)
}
// ✅ Správný vzor: Client wrapper se server children
'use client'
function TabPanel({ children, tabs }) {
const [activeTab, setActiveTab] = useState(0)
return (
<div>
<div className="tabs">
{tabs.map((tab, i) => (
<button key={i} onClick={() => setActiveTab(i)}>
{tab.label}
</button>
))}
</div>
{/* children mohou být server komponenty! */}
<div className="tab-content">
{children[activeTab]}
</div>
</div>
)
}
Caching a revalidace¶
Next.js poskytuje sofistikovaný caching systém pro RSC. Data načtená v serverových komponentách se automaticky cachují, a vývojáři mají kontrolu nad strategií revalidace. Existují tři hlavní přístupy: statické cachování (data se cachují na dobu neurčitou), časová revalidace (data se obnoví po uplynutí zadaného intervalu) a on-demand revalidace (data se obnoví programaticky po specifické události).
Správná cache strategie je kritická pro výkon RSC aplikací. Příliš agresivní cachování vede k zastaralým datům, zatímco nedostatečné cachování zbytečně zatěžuje databázi a zpomaluje odpovědi. V praxi se osvědčuje kombinace časové revalidace pro většinu dat s on-demand revalidací pro kritické aktualizace, například po uložení formuláře.
// Statické cachování — data se načtou jednou při buildu
async function StaticContent() {
const data = await fetch('https://api.example.com/content', {
cache: 'force-cache' // výchozí chování
})
return <Content data={data} />
}
// Časová revalidace — data se obnoví každých 60 sekund
async function DynamicPrices() {
const prices = await fetch('https://api.example.com/prices', {
next: { revalidate: 60 }
})
return <PriceList prices={prices} />
}
// On-demand revalidace — po server action
'use server'
import { revalidatePath, revalidateTag } from 'next/cache'
async function updateProduct(formData) {
await db.products.update(formData)
// Revalidace konkrétní stránky
revalidatePath('/products/' + formData.get('id'))
// Nebo revalidace podle tagu
revalidateTag('products')
}
Server Actions¶
Server Actions jsou těsně provázány s RSC a umožňují volat serverové funkce přímo z klientského kódu bez nutnosti vytvářet API endpointy. Funkce označené direktivou ‘use server’ se automaticky transformují na HTTP POST endpointy, které React volá transparentně. To dramaticky zjednodušuje full-stack vývoj a eliminuje boilerplate kód pro API vrstvy.
Server Actions podporují progresivní vylepšení — formuláře s Server Actions fungují i bez JavaScriptu, protože se zpracovávají jako standardní HTML formuláře. Toto je obzvláště důležité pro přístupnost a SEO, protože zajišťuje, že kritická funkcionalita je dostupná všem uživatelům bez ohledu na stav jejich JavaScriptu.
Praktické vzory a best practices¶
Při práci s RSC v produkčních aplikacích se osvědčilo několik vzorů. Prvním je co nejvíce logiky na serveru — pokud komponenta nepotřebuje interaktivitu, měla by zůstat serverová. Druhým vzorem je granulární client boundaries — místo označení celé stránky jako ‘use client’ je lepší vytvořit malé interaktivní ostrůvky obklopené serverovými komponentami.
Třetím důležitým vzorem je správné zacházení s props. Data předávaná z server do client komponent musí být serializovatelná — tedy žádné funkce, Date objekty ani cyklické reference. Místo toho je vhodné předávat primitivní typy, pole a jednoduché objekty. Pro komplexní data lze použít JSON serializaci s custom transformací.
Čtvrtým vzorem je error boundaries pro izolaci chyb. Každá sekce stránky by měla mít vlastní error boundary, aby chyba v jedné části nezbořila celou stránku. RSC přirozeně podporují error handling na úrovni route segmentů prostřednictvím error.tsx souborů.
Performance a metriky¶
RSC přinášejí měřitelné zlepšení klíčových webových metrik. Time to First Byte (TTFB) se zlepšuje díky streamování, protože server může začít odesílat obsah okamžitě. First Contentful Paint (FCP) se zrychluje, protože prohlížeč dostává HTML místo prázdné stránky čekající na JavaScript. Largest Contentful Paint (LCP) se optimalizuje díky paralelnímu načítání dat.
Nejvýraznější zlepšení je však v Total Blocking Time (TBT) a Time to Interactive (TTI), protože se dramaticky snižuje množství JavaScriptu, který musí prohlížeč parsovat a vykonat. V reálných aplikacích jsme pozorovali snížení velikosti JavaScript bundlu o 30-60 % po migraci na RSC.
Summary¶
React Server Components představují paradigmatický posun ve vývoji webových aplikací. Kombinují výhody server-side renderování s flexibilitou React komponentového modelu. Klíčem k úspěšné adopci je pochopení hranic mezi server a client komponentami, správné použití Suspense pro streaming a promyšlená cache strategie. RSC nejsou jen optimalizace — jsou to nový způsob myšlení o architektuře webových aplikací, který přibližuje React full-stack vývoji.