Streaming SSR
Streaming SSR
은 서버에서 렌더링된 HTML을 한번에 완성해서 보내는 방식이 아니라, 준비된 부분부터 점진적으로 스트리밍해서 클라이언트에 전달하는 기술이다. 이를 통해 사용자는 페이지의 중요한 콘텐츠를 더 빠르게 확인할 수 있다.
기존 SSR은 서버에서 모든 데이터를 처리한 뒤 완전한 HTML을 전송하는 반면, Streaming SSR은 서버가 데이터를 준비하는 즉시, HTML 조각을 스트림 형태로 보내고, 클라이언트는 이를 실시간으로 렌더링한다. React18에서는 renderToPipeableStream API
를 통해 구현할 수 있으며, 이 API는 서버에서 HTML을 조각 단위로 스트리밍할 수 있도록 지원한다.
renderToPipeStream(<App />, {
onShellReady() {
res.setHeader("Content-Type", "text/html")
stream.pipe(res)
}
})
이 방식의 큰 장점은 초기 로딩시간을 단축할 수 있다는 점이다. HTML의 일부라도 준비되는 즉시 클라이언트가 렌더링을 시작하므로 TTFB(Time to First Byte)가 개선된다. 특히 데이터가 많거나 복잡한 대규모 어플리케이션에서 효과적이며, 사용자가 중요한 콘텐츠를 먼저 확인할 수 있어 전반적인 사용자 경험도 향상된다. 다만 클라이언트에서 부분적으로 전송된 HTML을 제대로 Hydration할 수 있도록 설계가 필요하며, SEO나 캐싱 정책과의 호환성도 신중히 고려해야한다.
스트리밍된 데이터와 리액트의 Hydration 과정에서 발생하는 문제?
주요한 문제는 렌더링되는 HTML과 리액트의 상태 불일치이다.
HTML과 리액트의 상태 불일치 문제는 스트리밍된 HTML이 서버에서 먼저 클라이언트로 전송되고 리액트가 실행되기 전까지는 그냥 정적인 상태로만 보여지는데서 시작된다. 이후 Hydration 과정에서 이 HTML에 리액트의 상태와 이벤트 핸들러가 결합되는데, 이때 서버와 클라이언트 사이 데이터가 맞지 않으면 문제가 생길 수 있다.
예를 들어, 서버에서 렌더링된 데이터가 클라이언트에서 Hydration 시점에서 변경되어 있다면 리액트가 경고를 띄우거나, 예상치 못한 UI 동작이 나타날 수 있다. 또, 비동기 데이터 처리를 Suspense로 하고 있다면, 데이터가 늦게 로드되면서 UI가 달라질 가능성도 있다.
어떻게 해결하는가?
이런 문제를 막으려면, 서버와 클라이언트에서 동일한 데이터 소스를 사용해야 한다. 예를 들어, Tanstack Query 같은 라이브러리를 활용하면 데이터를 동기화하기가 훨씬 수월해진다. 또, Suspense와 fallback을 잘 활용하면, 데이터가 아직 준비되지 않았을 때도 안정적인 화면을 보여줄 수 있다. 이렇게 하면 사용자 입장에서 데이터가 로드되기 전에 UI가 흔들리는 문제를 줄일 수 있다.