← 염승준
2026.08

Streaming SSR from Scratch

프레임워크 없이 React Streaming SSR을 직접 만들고, 그 과정을 설명 페이지로 정리했습니다.

문제
  • getServerSideProps 기반 SSR에서는 서버가 HTML을 다 만들 때까지 사용자가 빈 화면만 봄
  • 사내 프로젝트가 Next.js 14 Pages Router라 Streaming SSR을 적용할 방법이 없었음
해결
  • renderToString의 한계에서 출발해 renderToPipeableStream과 onShellReady로 스트리밍 SSR을 직접 구현 (Hono + React 19)
  • 출력도 TTFB도 정상인데 스트리밍만 죽은 경우를 재현해, 숫자가 아니라 화면으로 확인해야 한다는 점을 정리
  • onShellReady와 onAllReady의 차이, boundary가 여럿일 때 자리는 선언 순·내용은 완료 순으로 채워지는 동작을 확인
  • Astro와 React islands로 과정을 7장짜리 설명 페이지로 정리
결과
  • 모든 수치는 직접 실행해 얻은 값
  • 한 줄만 바꿔 직접 확인해 볼 수 있는 실험 지점 제공

배경

처음 SSR을 접한 건 Next.js의 getServerSideProps였습니다. 서버에서 HTML을 만들어 보내는데, 그동안 사용자는 빈 화면만 보고 있었습니다.

다른 서비스들의 기술 블로그와 발표를 보며 Streaming SSR을 알게 됐습니다. 사내 프로젝트에 적용해 보고 싶었지만 Next.js 14 Pages Router라 방법이 없었고, 그래서 프레임워크 없이 직접 만들어 보기로 했습니다. mugglim/build-your-own-react-streaming-ssr이 출발점이 됐습니다.

다루는 내용

제목내용
1renderToString의 한계왜 기존 SSR API로는 스트리밍을 할 수 없는가
2스트리밍으로 바꾸기renderToPipeableStream과 onShellReady
3HTML 껍데기 붙이기문자열로 조립할 것인가, React 트리 안에 넣을 것인가
4스트리밍이 죽어도 티가 나지 않는다출력도 TTFB도 정상인데 화면만 다른 경우
5onShellReady와 onAllReady사람을 위한 응답과 기계를 위한 응답
6boundary가 여러 개일 때자리는 선언 순으로, 내용은 완료 순으로
7정리직접 만들며 알게 된 것들

모든 수치는 직접 실행해서 얻은 값입니다.

스트리밍이 죽어도 티가 나지 않는다

4장은 한 줄만 바꾸면 확인할 수 있습니다. 서버가 보내는 조각을 도착하는 대로 흘려보내지 않고 배열에 모았다가 한 번에 보내면, 응답 내용도 TTFB도 그대로인데 화면만 달라집니다. 스트리밍이 사라졌는데 숫자로는 드러나지 않는 것입니다.

그래서 스트리밍이 살아 있는지는 curl의 숫자보다 브라우저 화면으로 확인하는 편이 정확하다고 정리했습니다.

사람을 위한 응답, 기계를 위한 응답

5장은 onShellReady를 onAllReady로 한 줄 바꾸는 실험입니다. 그러면 응답에서 fallback과 교체 스크립트가 사라집니다. 껍데기부터 먼저 보여 주는 쪽은 기다리는 사람을 위한 응답이고, 모든 내용이 준비된 뒤 완성된 HTML을 한 번에 보내는 쪽은 기계를 위한 응답입니다.

따라 해 보기

저장소 코드는 6장 시점 상태입니다. 실행하면 세 영역이 0.5초, 1.5초, 3초에 각각 채워지는데, 가운데가 먼저, 아래가 다음, 맨 위가 마지막입니다. 선언한 순서와 채워지는 순서가 다르다는 6장의 내용이 그대로 보입니다.

curl -N -w '\nTTFB: %{time_starttransfer}s / Total: %{time_total}s\n' localhost:3001

-N(--no-buffer)을 주면 curl이 받은 내용을 도착하는 대로 출력해, 응답이 조각나서 오는 모습을 볼 수 있습니다.

사용한 것