TL;DR

  • Chromium에서 시험 중인 선언적 부분 업데이트(Declarative Partial Updates)는 응답을 열린 상태로 유지하며 콘텐츠를 순서와 무관하게 전송하는 기능으로, 중첩 콘텐츠를 서버에서 트리로 조립하거나 재귀 렌더링하지 않고도 표시할 수 있게 함.
  • 새 < ?marker> 요소와 <template for=""> 구문은 늦게 도착한 HTML을 지정된 자리표시자에 삽입하며, 같은 이름의 자리표시자를 반복해 콘텐츠를 이어 붙이는 방식임.
  • 중첩 답글을 기존 방식으로 렌더링하려면 중첩 단계마다 반복문을 쓰거나 재귀를 사용해야 하며, 두 방식 모두 HTML을 만들기 전에 전체 트리를 구성해야 하는 병목이 있음.
  • 평탄한 게시물 목록을 내려보내고 브라우저가 마커를 따라 페이지를 조립하게 하면 서버의 트리 구성 단계가 사라지며, 각 게시물을 생성하는 즉시 HTML로 스트리밍할 수 있음.
  • 게시물 1만 개는 브라우저에 부담을 주므로 로드 관리와 ‘댓글 더 보기’ 버튼은 여전히 필요하며, 이 방식은 중요한 콘텐츠를 먼저 표시하고 나머지를 나중에 렌더링하는 데 적합함.

선언적 부분 업데이트와 지연 도착 HTML

  • Chromium에서 선언적 부분 업데이트(Declarative Partial Updates) 기능이 시험 중이며, 제안은 콘텐츠의 순서에 구애받지 않는 스트리밍을 지원하는 새 < ?marker> 요소를 추가함.
  • 서버에서 필요한 HTML을 한꺼번에 만들거나 자바스크립트(JavaScript) 프레임워크에 제이슨(JSON)을 보내는 대신, 초기 응답에 HTML을 덧붙이고 <template for=""> 구문으로 해당 콘텐츠를 < ?marker name=""> 자리표시자에 삽입하는 방식임.
  • 늦게 도착한 템플릿은 대응하는 자리표시자에 자동으로 들어감. <slot> 요소와 달리 < ?marker>는 한 번 사용하면 소진되며, 같은 카드를 두 번 쓸 수 없음.
  • 게시물을 이어 붙여 피드를 만들려면 추가된 항목의 아래쪽에 같은 이름의 < ?marker name="post-abc123"> 자리표시자를 다시 배치하면 됨.
  • 핵심은 서버 연결을 열어 둔 채 HTML을 계속 보내는 것임. < ?marker> 요소가 새 HTML을 올바른 위치에 배치함.
  • 대형 언어 모델(LLM) 챗봇처럼 생성 속도가 서로 다른 콘텐츠를 스트리밍하는 사례가 대표적인 활용 방식일 수 있음. 마크다운(Markdown) 형식의 텍스트와 별도로 늦게 생성된 위젯을 전송해 응답에 삽입할 수 있음.
  • 이런 방식은 콘텐츠가 늦게 표시되면서 생기는 스타일 적용 전 콘텐츠 깜빡임(FOUC)과 최대 콘텐츠 렌더링(LCP) 관련 문제를 다뤄야 하지만, 지연 로드 HTML 렌더링은 웹 플랫폼에 새로운 기능을 더함.

스레드 응답 렌더링

  • 중요한 콘텐츠를 먼저 표시하고 화면 아래쪽의 나머지 콘텐츠가 언제 나타나는지는 크게 중요하지 않은 상황에서 < ?marker>를 고려할 수 있음.
  • 적용 사례로 게시물과 중첩 답글, 리뷰어 프로필 카드가 포함된 상품 리뷰, 중첩 댓글 스레드가 있는 대형 풀 리퀘스트(pull request), 관련 도서가 있는 저자 정보가 있음.
  • 이들은 전형적인 관계형 데이터 상황이며, 예시로 Reddit 스레드나 Bluesky 게시물처럼 답글이 중첩되는 구조를 다룸.
  • 전통적인 서버 처리 절차는 스레드의 최상위 게시물을 가져온 뒤 각 게시물의 답글, 각 답글의 중첩 답글을 차례로 가져오고 부모에 연결한 다음 트리를 순회해 모든 게시물의 HTML을 보내는 방식임.
  • 데이터베이스나 서버 백엔드가 트리를 구성한다고 가정하면, 가장 직접적인 렌더링 방식은 콘텐츠 단계마다 반복문을 중첩하는 것임.
  • 중첩 반복문을 하드 코딩하면 중첩 수준이 세 단계로 고정됨. 단계를 더 추가하려면 백엔드와 프런트엔드 작업이 필요하며, 프로그래밍의 ‘세 번 규칙(Rule of three)’도 어기게 됨.
  • 중첩을 하드 코딩하는 방식은 중첩 콘텐츠를 렌더링하는 ‘단순한 방식’이면서도 일반적인 방식임.

더 일반적인 중첩 렌더링

  • 철학적으로 UI는 렌더링하는 콘텐츠와 무관해야 하며, 중첩 콘텐츠가 가능하다면 렌더러도 이론상 무한 중첩을 지원해야 함.
  • 제품 관리자의 지라(Jira) 티켓에 최대 중첩이 두 단계라고 적혀 있어도 실제로는 최소 세 단계가 필요한 경우가 생긴다는 것이 실무적 고려 사항임.
  • 반복문을 중첩하는 대신 재귀를 쓰면 템플릿 코드를 정리하고 HTML을 페이지에 올리는 과정을 단순화할 수 있음. 원문은 중첩 반복문을 나쁘고 사용하는 사람을 어리석다고 놀리는 농담을 덧붙임.
  • 데이터 조회는 모든 답글을 단계별로 가져오는 대신 스레드의 게시물 전체를 평탄한 목록으로 가져온 뒤 트리로 조립하는 방식으로 빠르게 할 수 있음.
  • 능숙한 왼쪽 조인(LEFT JOIN) 에스큐엘(SQL) 문으로 처리할 수도 있지만, 일반적으로는 서버에서 트리를 조립하는 편이 쉬움.
  • 트리 구성 절차는 게시물 아이디(ID)를 키로 하는 맵을 만들고 각 게시물에 빈 답글 목록을 붙인 뒤, 부모가 없는 게시물을 루트로 두며 나머지는 부모 게시물의 답글에 추가하는 방식임.
  • 재귀 렌더러는 게시물의 사용자 이름과 본문을 출력하고, 각 답글에 같은 렌더링 함수를 호출해 중첩 HTML을 생성함.
  • 재귀 방식은 무한 중첩 콘텐츠를 더 견고하고 이해하기 쉽게 처리하며, 향후 변경에도 대응하기 쉬움. 중첩 수준은 렌더링 구성 요소의 문제가 아니라 데이터의 문제라는 관점임.
  • 두 방식 모두 코드 줄 수는 줄일 수 있지만, HTML을 렌더링하기 전에 전체 트리를 만들어야 한다는 병목은 동일함. 사용자는 데이터 조립을 기다린 뒤 트리 순회와 동기식 렌더링도 기다려야 하므로 스레드 표시 과정에 렌더링 차단 작업이 두 번 발생함.

순서와 무관한 HTML로 무한 중첩 스레드 구성

  • 페이지에 스레드의 루트 게시물용 < ?marker> 자리표시자를 두면, 렌더링 절차는 스레드의 게시물을 가져와 순회하며 각 게시물의 HTML을 보내는 것으로 단순해짐.
  • 각 게시물은 부모 게시물 아이디나 스레드 아이디를 대상으로 하는 <template> 안에 본문과 사용자 이름을 담고, 자식 게시물과 부모 게시물 위치에 대응하는 마커를 포함함.
  • 서버는 트리를 전혀 구성하지 않음. 템플릿 생성 직후 HTML을 클라이언트로 스트리밍하거나 전송할 수 있으며, 페이지가 콘텐츠를 조립함. 명령형으로 콘텐츠를 삽입하거나 재귀를 쓰거나 사전 조립할 필요가 없음.
  • 스레드에 게시물이 1만 개 있으면 브라우저에 큰 부담이 되므로 로드를 신중히 관리하고 ‘댓글 더 보기’ 버튼을 고려해야 함.
  • loadMoreComments() 함수는 새 댓글을 어디에 붙일지 알 필요 없이 페이지 아래쪽에 HTML을 덧붙이는 방식으로 단순화할 수 있음.
  • 중요한 렌더링 경로에서 부수 콘텐츠나 UI를 덜어낼 가능성이 있으며, HTML이 선언적 방식으로 콘텐츠와 관계를 스스로 조립하는 새로운 능력에 주목함.
  • HTML 임포트(HTML Imports)가 되살아날 가능성에 대한 기대를 덧붙임.