← 기술 지식

기술 지식 / 웹·모바일 / 웹 / 스타일·렌더링

서버 HTML과 hydration의 초기 상태 계약

보이는 화면과 동작하는 화면의 시점을 나누고 최초 렌더 일치·개인화 캐시·스트리밍 오류·부분 hydration을 설명합니다.

웹 · 원리·구현·운영
기술 지식 트리

SSR과 hydration의 핵심 계약은 서버 HTML로 만들어진 첫 DOM과 클라이언트의 첫 렌더가 같은 상태를 가리키고, 그 뒤 브라우저 전용 값을 의도적으로 갱신하는 것입니다. 이 노트는 표시 시점, 상호작용 시점, 스트리밍 부분 실패, 개인화 캐시를 하나의 초기 상태 흐름으로 구분합니다.

콘텐츠 표시와 상호작용 준비 시점

서버가 상품 HTML을 만들어 보내면 브라우저는 JavaScript 번들이 모두 준비되기 전에도 제목과 가격을 표시할 수 있습니다. 그러나 앱의 구매 버튼 핸들러는 번들 로드·실행 뒤에 연결될 수 있습니다. 콘텐츠 표시와 상호작용 준비는 같은 사건이 아닙니다.

SSR은 서버에서 HTML을 생성하는 방식이고 CSR은 브라우저 코드가 화면을 구성·갱신하는 방식입니다. 둘은 배타적이지 않습니다. hydration은 서버가 만든 DOM을 클라이언트의 초기 상태·이벤트 로직과 연결하는 과정이며, 보통 처음부터 빈 화면을 다시 그리는 것과 다릅니다.

서버·브라우저 최초 렌더와 초기 상태 일치

초기 상태의 추적은 S0 생성 → HTML 직렬화 → 클라이언트 S0 복원 → 첫 렌더 비교 → hydration → 브라우저 값으로 S1 갱신 순서로 기록합니다. 어느 단계에서 시각·로케일·API 버전이 달라졌는지 표시하면 hydration 경고와 개인화 데이터 노출을 같은 문제로 뭉개지 않고 진단할 수 있습니다.

서버에서 현재 시각을 렌더하고 브라우저가 수백 ms 뒤 자신의 시각으로 다시 계산하면 문자열이 다릅니다. 난수·로케일·시간대·viewport·localStorage도 초기 출력 불일치의 원인이 됩니다.

입력 불일치 원인 가능한 계약
현재 시각·난수 실행 시점마다 값이 다름 서버가 만든 초기값을 전달
브라우저 저장소 서버에서 접근 불가 동일 placeholder 뒤 클라이언트 갱신
사용자 로케일 서버 기본값과 다름 사용자 설정 공유·중립 초기 표현
API 데이터 HTML과 fetch의 버전 차이 같은 초기 snapshot 사용
같은 초기 상태로 연결한 뒤 갱신합니다초기 상태 S0. 서버 HTML(S0). 클라이언트 첫 렌더(S0). hydration · 이벤트 연결. 브라우저 상태 S1 갱신HTML 생성초기 데이터 전달기존 DOM일치하는 상태의도된 후속 변화초기 상태 S0서버 HTML(S0)클라이언트 첫 렌더(S0)hydration · 이벤트 연결브라우저 상태 S1 갱신
같은 초기 상태로 연결한 뒤 갱신합니다 화살표는 데이터와 렌더링 순서입니다. 서버 HTML과 클라이언트 최초 렌더가 같은 S0를 사용하고, 브라우저 전용 값은 연결 이후 S1으로 전환합니다.
그림을 글로 읽기
  • 초기 상태 S0
  • 서버 HTML(S0)
  • 클라이언트 첫 렌더(S0)
  • hydration · 이벤트 연결
  • 브라우저 상태 S1 갱신
  1. 초기 상태 S0 → 서버 HTML(S0): HTML 생성
  2. 초기 상태 S0 → 클라이언트 첫 렌더(S0): 초기 데이터 전달
  3. 서버 HTML(S0) → hydration · 이벤트 연결: 기존 DOM
  4. 클라이언트 첫 렌더(S0) → hydration · 이벤트 연결: 일치하는 상태
  5. hydration · 이벤트 연결 → 브라우저 상태 S1 갱신: 의도된 후속 변화

hydration 경고 억제와 초기 상태 계약

server:
    initial = load_page_snapshot()
    html = render(initial, clientOnlyValue=placeholder)
    send(html, safely_serialized_initial)

browser:
    initial = read_embedded_snapshot()
    hydrate(render(initial, clientOnlyValue=placeholder))
    after_initial_connection:
        update_client_only_value(read_browser_setting())

초기 상태를 HTML에 주입할 때는 들어갈 HTML·스크립트 문맥에 맞는 안전한 직렬화를 사용하고, 스크립트를 일찍 끝내 버릴 수 있는 문자열도 처리해야 합니다. 비밀번호·비밀 토큰처럼 클라이언트에 전달하면 안 되는 값은 초기 snapshot에 넣지 않습니다. 프레임워크가 불일치를 일부 복구하더라도 DOM 재생성·상태 손실·잘못된 연결 비용이 남을 수 있으므로, 넓게 경고를 숨기기보다 서버와 클라이언트가 같은 최초 상태를 계산하게 해야 합니다.

JS 번들이 아직 준비되지 않은 순간에도 native 링크와 일반 폼은 브라우저 기본 동작으로 진행될 수 있지만, 앱 전용 핸들러가 필요한 버튼은 같은 방식으로 동작하지 않습니다. 따라서 준비 전 버튼을 비활성·대기 중 어떤 상태로 보일지 정하고, 클릭을 큐잉·재생하는 프레임워크라면 같은 입력이 두 번 실행되지 않는지 확인해야 합니다. 이런 재생 지원을 모든 프레임워크의 일반 보장으로 가정하지 않습니다.

스트리밍 영역별 완료와 부분 실패

서버가 전체 데이터를 기다리지 않고 준비된 HTML과 fallback을 먼저 보내면 첫 콘텐츠가 빨라질 수 있습니다. 대신 영역마다 완료 시점이 달라지고, HTTP 헤더와 본문 일부를 이미 보낸 뒤 서버 오류가 나면 상태 코드를 새 500으로 바꾸기 어려울 수 있습니다. 구현에서는 실패할 수 있는 영역을 화면의 오류 경계로 나누고, 이미 전송된 영역·실패한 영역·재시도 가능한 작업과 부분 완료 상태를 사용자에게 구분해 보여줘야 합니다.

느린 프록시 버퍼링이 조각을 모아 전달하면 서버에서 스트리밍해도 사용자는 한꺼번에 볼 수 있습니다. 클라이언트 연결 종료 시 서버의 남은 조회·렌더 작업을 취소하거나 추적할 정책도 필요합니다.

부분 hydration은 상호작용이 필요한 영역에만 클라이언트 실행을 붙입니다. 정적 본문에는 불필요한 번들을 줄일 수 있지만 섬 사이의 상태 공유·이벤트·데이터 버전과 늦은 활성화 경계가 생깁니다. 단순히 여러 조각으로 나누는 것만으로 전체 비용이 사라지지는 않습니다.

개인화 캐시와 구버전 번들 호환성

사용자 A의 HTML을 공유 캐시에 넣으면 B에게 A의 정보가 전달될 수 있습니다. hydration이 나중에 B의 데이터로 바꾸어도 이미 노출된 내용을 되돌리지 못합니다. 캐시 키·private/no-store·공개 셸과 개인화 데이터 경계를 설계해야 합니다.

오래 캐시된 번들이 새 HTML·새 API와 만날 수 있습니다. 새 enum이나 필수 필드 때문에 코드가 실패할 수 있으므로 서버·초기 payload·번들의 호환 조합을 검증합니다. hydration 불일치와 API 계약 오류는 증상과 해결 경계가 다릅니다.

원문 HTML과 클라이언트 첫 렌더 비교

검증 결과에는 보이는 시각, handler 준비 시각, 첫 입력의 실제 처리, hydration 경고, 스트림 중단 시점을 별도 필드로 남깁니다. 원문 HTML이 빠르게 도착했다는 결과만으로 상호작용이 준비되었다고 판정하지 않으며, JS 비활성·느린 번들·개인화 캐시 조건의 결과도 서로 섞지 않습니다.

JS를 끈 화면과 느린 번들에서 시작해, 다른 로케일·시간대, 저장소 차단, 스트림 중간 실패를 각각 재현합니다. 각 조건의 원문 HTML을 캡처하고, JS가 실행되는 조건에서는 클라이언트 최초 상태와 hydration 경고, 이벤트 handler가 준비되는 시점, 그 전에 누른 입력의 실제 반응을 시간순으로 기록합니다. JS가 꺼진 조건에서는 브라우저 기본 동작을 별도로 확인하고, 실행되는 조건에서는 화면이 보인 시각과 동작이 준비된 시각, 스트림 조건에서는 부분 실패 시점을 비교합니다.

첫 표시가 빨라져도 번들 실행이 메인 스레드를 오래 점유하면 입력 지연은 나빠질 수 있습니다. SSR·CSR의 이름보다 초기 일치·개인화 안전·상호작용 비용의 계약을 기준으로 선택해야 합니다.

후속·관련 개념

함께 읽을 문서

전체 기술 지식
선택 연습 · 관련 질문 2개

질문은 이해를 확인하는 보조 자료입니다. 학습 경로와 문서의 범위는 질문 수와 독립적입니다.