← 목차5 / 7

05

onShellReady와 onAllReady

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

콜백이 두 종류인 이유

2장에서 renderToPipeableStream이 콜백을 받는다고 했습니다. 그중 완성 시점을 알려주는 것이 두 개 있습니다.

콜백 호출 시점
onShellReady Suspense 바깥 영역이 완성됐을 때
onAllReady 모든 Suspense 내부까지 완성됐을 때

지금까지는 onShellReady를 썼습니다. 여기서 pipe를 부르니 shell이 준비되는 즉시 응답이 나갔습니다.

onAllReady에서 pipe를 부르면 전부 준비된 뒤에 한 번에 나갑니다. 스트리밍의 이점이 사라집니다.

스트리밍이 좋은 것이라면 왜 이런 선택지가 있을까요.

응답에서 대부분이 사라졌다

콜백 이름 한 줄만 바꿔봤습니다.

onAllReady() {
  pipe(passThrough);
},

onShellReady일 때는 이랬습니다.

<!--$?--><template id="B:0"></template>
<div>loading...</div>
<!--/$-->
...
<div hidden id="S:0">
  <ul>
    ...
  </ul>
</div>
<script>
  $RB=[];$RV=function(a){...};$RC=function(a,b){...};$RC("B:0","S:0")
</script>

onAllReady로 바꾸니 이렇게 됐습니다.

<!--$-->
<ul>
  ...
</ul>
<!--/$-->

목록은 그대로 있는데 나머지가 전부 없어졌습니다.

완성된 상태로 보내니 나중에 채운다는 표시 자체가 불필요해진 것입니다.

주석 마커도 바뀌었습니다.

마커 의미
onShellReady <!--$?--> 미해결
onAllReady <!--$--> 해결됨
서버가 보낸 응답
<div id="root">
<h1>Todo List</h1>
<!--$?-->
<template id="B:0"></template>
<div>loading...</div>
<!--/$-->
</div>
<script>
$RB=[];$RV=function(a){ ...약 700바이트... };
$RC=function(a,b){ ... };
$RC("B:0","S:0")
</script>
  • 자리 표시자
  • fallback
  • 화면 밖 도착분
  • 교체 스크립트
측정과 성격
TTFB
0.006s
첫 화면
0.006s — loading...
사용자 체감
즉시 fallback, 2초 뒤 목록
JS 의존
교체에 JS 필요
적합한 대상
사람

사람은 2초간 흰 화면을 견디기 어렵지만 loading...은 참아준다. 크롤러는 화면을 보지 않으므로 loading...을 그냥 콘텐츠로 읽는다.

TTFB가 재는 것은 무엇인가

TTFB: 0.013438s / Total: 2.032018s

2초가 나올 줄 알았는데 13밀리초입니다.

curl 문서를 찾아보니 time_starttransfer는 시작부터 첫 바이트를 받은 시점까지를 잽니다. 그런데 HTTP 응답에서 먼저 도착하는 것은 헤더입니다. 본문이 2초 뒤에 오더라도 헤더가 먼저 도착하면 TTFB는 짧게 찍힙니다.

The time it took from the start until the first byte is received by libcurl.

— curl_easy_getinfo, CURLINFO_STARTTRANSFER_TIME

브라우저로 열어보면 2초간 빈 화면이 명확하게 보입니다.

4장에 이어 두 번째입니다. TTFB는 스트리밍 여부를 판정하는 데 쓸 수 없습니다.

믿을 수 있는 것은 두 가지입니다.

그러면 onAllReady는 왜 존재하는가

스트리밍은 JS가 실행된다는 전제 위에서 동작합니다.

onShellReady 응답의 목록은 <div hidden>으로 도착하고, $RC가 실행되어야 제자리를 찾습니다. JS를 실행하지 않는 쪽에서는 그 목록이 없는 것과 같습니다.

받는 쪽이 사람이 아니라면 이야기가 달라집니다.

정적 생성은 빌드 시점에 HTML을 미리 만들어 파일로 저장해두는 방식입니다. 요청이 올 때마다 렌더링하지 않고 그 파일을 그대로 내보냅니다. 저장할 대상이 완성된 HTML이어야 하니, 조금씩 흘려보내는 것에는 의미가 없습니다.

크롤러는 페이지 내용을 읽어 색인에 넣는 것이 목적입니다. 화면을 보는 것이 아니라 HTML을 읽습니다.

둘 다 HTML 자체가 결과물입니다. 보통 사람에게는 HTML이 화면으로 가는 중간 단계지만, 이들에게는 HTML이 최종 산출물입니다. 빠른 첫 바이트가 아무 의미가 없습니다.

React 공식 문서도 크롤러와 정적 생성에 onAllReady를 권합니다.

onShellReady onAllReady
사용자 체감 6ms에 loading... 2초간 빈 화면
응답 크기 큼 작음
fallback 있음 없음
JS 의존 교체에 필요 불필요

한 문장으로 정리하면 이렇습니다.

onShellReady는 사람을 위한 것, onAllReady는 기계를 위한 것.

사람은 화면을 보므로 2초간 빈 화면이 손해입니다. 그리고 브라우저가 JS를 실행하니 교체도 동작합니다. 기계는 화면을 보지 않습니다. 필요한 것은 완성된 HTML입니다.