--- id: wiki-2026-0508-server-side-rendering-ssr title: Server Side Rendering (SSR) category: 10_Wiki/Topics status: verified canonical_id: self aliases: [SSR, Server Rendering, isomorphic rendering] duplicate_of: none source_trust_level: A confidence_score: 0.95 verification_status: applied tags: [ssr, react, nextjs, rendering, performance] raw_sources: [] last_reinforced: 2026-05-10 github_commit: pending tech_stack: language: TypeScript framework: Next.js 15 / React 19 --- # Server Side Rendering (SSR) ## 매 한 줄 > **"매 SSR 은 server 에서 HTML 을 생성해 first paint 를 빠르게, SEO 를 가능하게"**. 2026 기준 React 19 의 streaming SSR + Suspense + Server Components 가 표준. 매 trade-off 는 server compute cost vs CSR-only 의 blank-screen 제거. 매 modern 변형: SSG (build-time), ISR (revalidate), PPR (Partial Prerender). ## 매 핵심 ### 매 rendering modes - **CSR**: 매 client only — slow first paint, no SEO without JS - **SSR**: 매 server-render full HTML per request - **SSG**: 매 build-time HTML (static) - **ISR**: 매 SSG + on-demand or time-based revalidation - **PPR**: 매 partial prerender — static shell + dynamic holes (Next.js 15) ### 매 hydration - Server HTML 송신 → client JS 가 attach (event handler 연결) - Streaming SSR: HTML 을 chunk 로 진행 송신 - Selective hydration: visible / interacted 부분 우선 ### 매 응용 1. Marketing / blog (SEO + fast paint). 2. E-commerce PDP (per-user pricing + SEO). 3. Dashboard shells (PPR — static shell + dynamic data). ## 💻 패턴 ### Next.js 15 RSC + streaming ```tsx // app/page.tsx import { Suspense } from "react"; import { ProductGrid } from "./product-grid"; export default function Page() { return (

Products

매 loading…}>
); } // app/product-grid.tsx (RSC, runs on server) import { db } from "@/lib/db"; export async function ProductGrid() { const products = await db.product.findMany({ take: 20 }); return ( ); } ``` ### Partial Prerendering (PPR) ```tsx // app/dashboard/page.tsx export const experimental_ppr = true; import { Suspense } from "react"; import { StaticHeader } from "./header"; import { LiveMetrics } from "./metrics"; export default function Page() { return ( <> {/* prerendered */} }> {/* dynamic, streamed */} ); } ``` ### ISR with revalidate ```tsx // app/posts/[slug]/page.tsx export const revalidate = 60; // every 60s export default async function Page({ params, }: { params: Promise<{ slug: string }> }) { const { slug } = await params; const post = await fetch(`https://api.example.com/posts/${slug}`, { next: { revalidate: 60, tags: [`post:${slug}`] } }).then((r) => r.json()); return
{post.title}
; } ``` ### On-demand revalidation ```typescript // app/api/revalidate/route.ts import { revalidateTag } from "next/cache"; export async function POST(req: Request) { const { tag } = await req.json(); revalidateTag(tag); return Response.json({ ok: true }); } ``` ### Pure React 19 streaming SSR ```typescript // server.ts import { renderToReadableStream } from "react-dom/server"; import App from "./App"; export default async function handler(req: Request): Promise { const stream = await renderToReadableStream(, { bootstrapModules: ["/client.js"], onError: (err) => console.error(err), }); await stream.allReady; // remove for true streaming return new Response(stream, { headers: { "content-type": "text/html" }, }); } ``` ### Client hydration entry ```typescript // client.ts import { hydrateRoot } from "react-dom/client"; import App from "./App"; hydrateRoot(document, ); ``` ### Cache headers for SSR ```typescript // app/api/data/route.ts export async function GET() { return Response.json( { data: 42 }, { headers: { "Cache-Control": "public, s-maxage=60, stale-while-revalidate=300", }, }, ); } ``` ### Server-only utility ```typescript // lib/server-only.ts import "server-only"; // 매 import in client component throws import { db } from "./db"; export async function loadSecret() { return db.secret.findFirst(); } ``` ### Edge runtime SSR ```typescript // app/page.tsx export const runtime = "edge"; export default async function Page() { const data = await fetch("https://api.example.com/edge").then((r) => r.json()); return
{JSON.stringify(data)}
; } ``` ## 매 결정 기준 | 상황 | Approach | |---|---| | Static marketing page | SSG (`generateStaticParams`) | | Per-user dynamic | SSR / RSC | | Mostly static + small dynamic | **PPR** | | Data refreshes minutes-scale | ISR with revalidate | | Internal app, no SEO | CSR (Vite SPA) sufficient | | Low latency global | Edge runtime SSR | **기본값**: Next.js 15 App Router + RSC + Suspense streaming + PPR where applicable. ## 🔗 Graph - 부모: [[Rendering Strategies]] · [[Web Performance]] - 변형: [[CSR]] · [[SSG]] · [[ISR]] · [[Streaming SSR]] - 응용: [[Remix]] · [[SvelteKit]] · [[Nuxt]] - Adjacent: [[React Server Components]] · [[Hydration]] · [[Suspense]] ## 🤖 LLM 활용 **언제**: rendering strategy 결정, SEO + first paint 최적화, RSC + Suspense 설계. **언제 X**: 매 internal admin tool with auth-only access (CSR 충분), 매 매우 dynamic real-time app (WebSocket-driven). ## ❌ 안티패턴 - **SSR everything**: 매 unnecessary server compute. 매 marketing page → SSG. - **No streaming**: 매 await all data → blank for 5s. 매 Suspense + streaming. - **Hydration mismatch**: server `Date.now()` vs client → 매 warning. `suppressHydrationWarning` 또는 client-only render. - **Secret in client component**: 매 env var leak. `server-only` import. - **Massive RSC payload**: 매 props 에 huge JSON. 매 boundary 재설계. - **Forgetting cache tags**: ISR 인데 invalidation 못 함. ## 🧪 검증 / 중복 - Verified (Next.js 15 docs, React 19 docs, Vercel blog 2026). - 신뢰도 A. ## 🕓 Changelog | 날짜 | 변경 | |---|---| | 2026-05-08 | Phase 1 | | 2026-05-10 | Manual cleanup — SSR full content |