#30. Streaming, Server/Client Composition Patterns
1) Streaming이란?
기본 SSR은 “데이터 다 받고 → HTML 만들고 → 내려보내고 → Hydration”이 순차(블로킹) 로 진행돼서, 느린 데이터 하나가 있으면 페이지 전체가 늦어질 수 있습니다. Next.js의 Streaming은 HTML을 작은 청크로 나눠 준비되는 부분부터 점진적으로 전송해, 사용자가 더 빨리 화면을 보기 시작하도록 만드는 방식입니다. Next.js
Streaming은 React의 <Suspense> 와 결합되어 “fallback UI를 먼저 보여주고 → 준비되면 교체”하는 흐름으로 동작합니다. Next.js+1
또한 App Router에서 페이지/레이아웃은 기본적으로 Server Component이며, 서버에서 렌더링된 결과를 (필요하면 캐시하고) 클라이언트로 스트리밍할 수 있습니다. Next.js
2) Streaming 구현 방법 2가지
A. 라우트 세그먼트 단위: loading.tsx (가장 권장)
app/대상폴더/loading.tsx를 만들면, 해당 세그먼트는 자동으로 <Suspense> 경계가 생기고 즉시 로딩 UI를 보여주며 스트리밍됩니다. Next.js
// app/product-reviews/loading.tsx
export default function Loading() {
return <p>Loading...</p>;
}
Next.js는 loading.tsx 방식에 최적화가 들어가므로, “세그먼트 로딩”은 이 방식이 권장됩니다. Next.js
B. 컴포넌트 단위: 직접 <Suspense> 감싸기 (부분만 쪼개서 먼저 보여주기)
loading.tsx는 “세그먼트 전체”에 걸리기 때문에, 한 페이지 안에서 Product는 먼저(2초), Reviews는 나중(4초) 처럼 부분 스트리밍을 원하면 컴포넌트별 <Suspense> 를 둡니다. Next.js 문서에서도 loading 외에 “수동 Suspense 경계”를 권장/지원한다고 안내합니다. Next.js+1
// app/product-reviews/page.tsx
import { Suspense } from "react";
import { Product } from "@/components/product";
import { Reviews } from "@/components/reviews";
// (필요 시) 요청마다 스트리밍을 보려면 강제 동적
export const dynamic = "force-dynamic";
export default function ProductReviews() {
return (
<div>
<h1>Product Reviews</h1>
<Suspense fallback={<div>Loading Product...</div>}>
<Product />
</Suspense>
<Suspense fallback={<div>Loading Reviews...</div>}>
<Reviews />
</Suspense>
</div>
);
}
주의 포인트
- <Suspense> 경계를 안 나누면 “가장 느린 것” 기준으로 한 번에 붙는 느낌이 날 수 있습니다. React는 같은 경계 안을 “하나의 단위”로 취급할 수 있다고 설명합니다. React
3) Server and Client Composition Patterns (서버/클라이언트 조합 설계)
App Router의 핵심은 “Server 기본 + Client는 꼭 필요한 곳만” 입니다. Next.js는 Server/Client를 이렇게 구분해 사용하라고 안내합니다. Next.js+1
- Server Component에 적합: DB/백엔드 리소스 접근, 비밀키/토큰 사용, 클라이언트로 보내는 JS 감소 Next.js+1
- Client Component에 적합: 이벤트 핸들러, state/effect, 브라우저 API 사용 Next.js+1
패턴 1) “Client를 트리 아래로 내리기”
레이아웃 전체를 use client로 만들지 말고, 인터랙션 필요한 조각만 Client로 분리하는 패턴입니다(번들 감소). Next.js+1
패턴 2) “Client에서 Server를 import하지 말고, Server를 children/props로 꽂기”
가장 많이 실수하는 규칙입니다.
- ❌ Client Component가 Server Component를 import하는 건 지원되지 않습니다. Next.js+1
- ✅ 대신 부모 Server Component에서 둘 다 import한 뒤, Server Component를 Client의 children(slot) 로 넘깁니다. Next.js
(개념만 기억하시면 됩니다: “Client 모듈 그래프 안으로 Server를 끌고 들어오지 말고, Server에서 조립해서 넣어라”)
패턴 3) “Server → Client props는 직렬화(Serializable)되어야 함”
Server에서 가져온 데이터를 Client로 props로 내릴 땐 직렬화 가능한 값이어야 합니다. (함수/클래스/특수 객체 등은 불가) Next.js+1
4) import "server-only"; 는 왜 쓰나요?
Server/Client 컴포넌트가 섞이면, 실수로 “서버 전용 코드(비밀키/DB/fs)”를 클라이언트에서 import하는 사고가 날 수 있습니다.
이를 막기 위해 Next.js는 server-only 패키지로 모듈을 표시하라고 안내합니다.
- 서버 전용 모듈에 import 'server-only'를 넣으면
- 그 모듈이 Client 환경에서 import될 경우 빌드 에러로 막아줍니다. Next.js+1
사용 방법
pnpm add server-only
// lib/secret-data.ts
import "server-only";
export function getSecret() {
return process.env.API_SECRET; // 예: 서버에서만 안전
}
참고: use server는 “Server Actions”용 지시자이고, server-only는 “이 모듈은 절대 클라이언트로 가면 안 됨”을 강제하는 용도라 성격이 다릅니다. (헷갈리기 쉬운 포인트입니다.) Next.js