Suspense와 스트리밍 — 부분 렌더로 빠른 응답
느린 한 영역 때문에 빠른 모든 영역이 같이 늦지 않게.
대시보드 페이지를 만들었는데 — 헤더는 즉시 보이는데 차트가 3초나 걸린다. 옛 SSR 이면 차트 끝날 때까지 화면이 통째로 백지. 사용자는 3초 동안 빈 페이지를 본다.
Next.js 의 Streaming SSR 은 이 흐름을 깬다. 헤더는 즉시 HTML 로 보내고, 차트는 준비되는 대로 나중에 같은 응답에 이어 붙인다. 그 마법의 입구가 <Suspense>.
1. Suspense 의 기본 — fallback 으로 분리
가장 단순한 예. 한 페이지 안에서 빠른 부분과 느린 부분을 분리.
사용자가 보는 순서:
- 0ms — 헤더·StatsHeader·ChartSkeleton 까지 HTML 도착. 차트 자리엔 스켈레톤.
- 3000ms — SlowChart 의 fetch 끝남. 진짜 차트 HTML 이 같은 응답에 이어붙어 도착. React 가 자동으로 스켈레톤을 진짜 차트로 교체.
핵심은 응답이 한 번에 끝나지 않는다는 것. Next 가 HTTP 응답을 "닫지 않고" 유지하다가 준비되는 컴포넌트를 chunked transfer 로 추가 전송. 사용자 입장에선 그냥 페이지가 점진적으로 채워지는 느낌.
2. 여러 Suspense — 각자 독립 로딩
여러 느린 부분을 각자 따로 보여줄 수 있다. 차트와 테이블이 둘 다 느리면 둘이 서로 안 기다린다.
2초쯤 차트가 채워지고, 4초쯤 테이블이 채워진다. 둘이 동시 시작이라 4초가 최댓값. 옛 방식이면 둘 합쳐 6초.
3. loading.tsx vs <Suspense> 차이
11편의 loading.tsx 가 결국 같은 메커니즘인데 — 두 가지 차이가 있다.
| 구분 | loading.tsx | <Suspense> 직접 |
|---|---|---|
| 범위 | 그 폴더 page 전체 | 감싼 부분만 |
| 위치 | 특수 파일 컨벤션 | 코드 어디든 |
| 여러 개 | 폴더당 1개 | 한 페이지에 N개 |
| 사용 시점 | 페이지 전환·초기 로드 | 한 페이지 안 부분 로드 |
둘은 충돌하지 않는다. loading.tsx 는 첫 도착까지의 큰 그림, <Suspense> 는 그 안에서 부분 부분. 함께 쓰면 깊이감 있는 UX 가 가능.
4. 데이터 fetching 과 결합 — async + Suspense
Suspense 가 진짜로 멈추는 트리거는 async Server Component 안의 await. 그 await 가 끝나기 전엔 그 컴포넌트 위치에 fallback 이 표시된다.
이 컴포넌트가 <Suspense> 안에 있으면 await 동안 fallback. 끝나면 진짜 결과. useState·useEffect 없이 자연스럽게 작동한다.
5. 흔한 함정과 베스트 프랙티스
use() 훅으로 promise 만 props 로 받기.
베스트 프랙티스 4가지:
- 스켈레톤은 실제 UI 형태 모방 — 카드 자리에 비슷한 회색 블록. 모양이 크게 변하면 레이아웃 시프트(CLS) 가 발생해 SEO 점수 떨어짐.
- 너무 잘게 쪼개지 말 것 — Suspense 가 5개 넘어가면 화면이 어수선. 보통 페이지당 2~3개가 sweet spot.
- 위 영역 먼저 빠르게 — 사용자가 처음 보는 영역(헤더·hero)은 Suspense 밖에. 아래로 갈수록 느려도 OK.
- error.tsx 와 함께 — Suspense 안에서 throw 가 일어나면 가장 가까운
error.tsx가 잡는다. 한 영역만 깨지고 나머진 그대로.
요약 — 12편 좌표
여기까지 정리. Streaming SSR 의 입구는 <Suspense fallback={...}>. 안쪽 async 컴포넌트의 await 가 끝날 때까지 fallback, 끝나면 진짜 결과로 자동 교체. 여러 Suspense 는 각자 독립 로딩. loading.tsx 는 페이지 전체용, <Suspense> 는 부분용. 스켈레톤 모양 맞추기·너무 잘게 쪼개지 않기·위 영역 먼저. 다음 편에선 요청을 가로채는 — Middleware 를 본다.
다음 편 예고 — Middleware
요청 가로채기·리디렉트·인증 가드. 13편.