← Blog
Computer Science / 네트워크

브라우저는 화면을 어떻게 그리는가 — 렌더링 · 저장소 · CORS · 캐싱

면접에서 "말로 설명한다"는 가정으로 작성. 답을 외우기보다 "왜"와 "트레이드오프"를 설명할 수 있는 것이 목표입니다.


2-1. 브라우저 렌더링 과정

  1. HTML 파싱 → DOM 트리 생성
  2. CSS 파싱 → CSSOM 트리 생성
  3. DOM + CSSOM 결합 → 렌더 트리 (화면에 보이는 것만)
  4. 레이아웃(Reflow): 각 요소의 위치·크기 계산
  5. 페인트(Paint): 픽셀로 그리기
  6. 컴포지팅: 레이어를 합성해 최종 화면 출력

JS는 DOM 파싱을 막을 수 있어 async/defer로 로딩을 제어합니다.

  • async: 다운로드 끝나는 즉시 실행(순서 보장 X)
  • defer: HTML 파싱이 끝난 뒤 순서대로 실행

2-2. Reflow vs Repaint

  • Reflow(레이아웃): 요소의 크기·위치가 바뀌어 레이아웃을 다시 계산 (비용 큼)
  • Repaint: 위치는 그대로지만 색상 등 외형만 다시 그림

최적화:

  • 레이아웃을 유발하는 속성(width, top 등) 대신 transform, opacity 사용 → 컴포지터에서 처리되어 reflow/repaint 회피
  • DOM 변경은 모아서 한 번에 (DocumentFragment 등)
  • 레이아웃 값을 읽고 쓰는 작업을 번갈아 하면 강제 동기 레이아웃(layout thrashing) 발생 → 읽기/쓰기 분리

2-3. Cookie / LocalStorage / SessionStorage

CookieLocalStorageSessionStorage
용량~4KB~5-10MB~5-10MB
만료설정 가능영구탭 닫으면 삭제
서버 전송매 요청마다 자동안 됨안 됨
용도인증 토큰, 세션지속 데이터일시적 데이터

쿠키는 HttpOnly(JS 접근 차단, XSS 방어), Secure(HTTPS만), SameSite(CSRF 방어) 속성으로 보안을 강화합니다.


2-4. CORS

브라우저의 동일 출처 정책(Same-Origin Policy) 때문에 다른 출처(프로토콜+도메인+포트)로의 요청이 제한되는데, 서버가 Access-Control-Allow-Origin 등의 헤더로 허용하면 통신이 가능합니다.

  • 단순 요청이 아니면(커스텀 헤더, PUT/DELETE 등) 브라우저가 먼저 OPTIONS Preflight 요청을 보내 허용 여부를 확인
  • 해결: 서버에서 CORS 헤더 설정(정석), 개발 중엔 프록시 서버 사용

2-5. 브라우저에 URL을 입력하면 무슨 일이?

  1. URL 파싱
  2. DNS 조회로 IP 획득
  3. TCP 연결 (+ HTTPS면 TLS 핸드셰이크)
  4. HTTP 요청 전송
  5. 서버 응답 수신
  6. 브라우저 렌더링(2-1 과정)

2-6. CSR / SSR / SSG

  • CSR(Client-Side Rendering): 빈 HTML + JS로 클라이언트에서 렌더. 초기 로딩 느리고 SEO 불리, 이후 인터랙션은 빠름
  • SSR(Server-Side Rendering): 서버에서 완성된 HTML 생성. 초기 로딩·SEO 유리, 서버 부하 있음. 하이드레이션(서버가 보낸 HTML에 JS 이벤트를 연결하는 과정)으로 인터랙티브화
  • SSG(Static Site Generation): 빌드 시 HTML 미리 생성. 가장 빠르고 SEO 좋지만 데이터가 자주 바뀌면 부적합

2-7. HTTP 버전별 개선점

기초 용어

  • RTT(Round-Trip Time): 요청을 보내고 응답을 받기까지의 왕복 시간. 연결 수립에 몇 RTT가 필요한지가 초기 속도를 좌우. 거리가 멀수록 1 RTT가 커짐(한국↔미국은 수백 ms).
  • TLS(Transport Layer Security): 통신을 암호화하는 보안 프로토콜. HTTP + TLS = HTTPS. 암호화·무결성·인증을 제공. 과거 SSL의 후속(현재 TLS 1.3). 연결 시 암호화 방식·키를 합의하는 과정이 TLS 핸드셰이크.
  • HOL(Head-of-Line) Blocking: 앞의 요청이 끝나야 뒤가 처리되어 줄줄이 막히는 현상.

HTTP/1.1 — 순차 처리, HOL Blocking

한 TCP 연결에서 요청을 순차 처리. 앞 요청 응답이 늦으면 뒤가 대기(HOL Blocking). 브라우저는 도메인당 6개 연결을 열어 우회했지만 근본 해결은 아니었음. (CSS 스프라이트, 도메인 샤딩 같은 꼼수 등장)

HTTP/2 — 멀티플렉싱

  1. 멀티플렉싱(핵심): 한 TCP 연결에서 여러 요청·응답을 동시 처리. 데이터를 "스트림"으로 쪼개 섞어 보내고 다시 조립 → HTTP 레벨 HOL Blocking 해결
  2. 헤더 압축(HPACK): 반복 헤더 압축·테이블 관리로 중복 전송 감소
  3. 바이너리 프로토콜: 텍스트 대신 바이너리 프레임 → 파싱 효율 향상
  4. 서버 푸시: 요청 전 리소스 선전송. 실효성 문제로 사실상 폐기 수순

HTTP/2의 한계 — TCP HOL Blocking: HTTP 레벨은 풀었지만 여전히 TCP 위에서 동작. TCP는 순서를 보장하므로 패킷 하나가 유실되면 뒤 패킷이 다 와도 재전송까지 대기. 멀티플렉싱으로 여러 스트림이 한 연결을 공유하기에, 한 스트림의 손실이 무관한 다른 스트림까지 막음 → TCP 레벨 HOL Blocking.

HTTP/3 — QUIC(UDP 기반)

HTTP/2:  HTTP/2 → TLS → TCP
HTTP/3:  HTTP/3 → QUIC(TLS 1.3 내장) → UDP
  • TCP HOL Blocking 해결: QUIC은 스트림을 독립 관리 → 한 스트림 패킷 유실이 다른 스트림에 영향 없음
  • 연결 수립이 빠름: TCP(3-way) + TLS를 따로 하면 2 RTT인데, QUIC은 연결+암호화를 통합해 01 RTT
  • 연결 마이그레이션: Connection ID로 연결 식별 → Wi-Fi ↔ LTE 전환에도 연결 유지(모바일 강점)

HTTP/3의 단점

  1. UDP 차단: 일부 방화벽·네트워크 장비가 UDP를 막아 TCP(HTTP/2)로 폴백 필요
  2. CPU 부하: TCP는 커널·하드웨어가 최적화돼 있지만 QUIC은 상당 부분 사용자 공간에서 처리 → CPU 사용량 높음
  3. 성숙도: 도구·디버깅·생태계가 TCP만큼 무르익지 않아 운영 난도 있음
  4. 구현 복잡도: 혼잡 제어·재전송·순서 보장을 직접 구현해야 함

정리 표

HTTP/1.1HTTP/2HTTP/3
전송 계층TCPTCPQUIC(UDP)
요청 처리순차멀티플렉싱멀티플렉싱
HTTP HOL있음해결해결
TCP HOL있음남음해결
헤더 압축XHPACKQPACK
핸드셰이크TCP+TLS 분리TCP+TLS 분리통합(0~1 RTT)
형식텍스트바이너리바이너리

면접 멘트: "HTTP/2는 멀티플렉싱으로 HTTP 레벨 HOL Blocking을 풀었지만, TCP 순서 보장 때문에 패킷 유실 시 TCP 레벨 HOL Blocking이 남았습니다. HTTP/3는 UDP 기반 QUIC으로 스트림을 독립 처리하고 핸드셰이크를 통합해 연결도 빨라졌습니다. 다만 UDP 차단 시 폴백이 필요하고 CPU 부하가 높습니다."


2-8. 캐싱 전략

브라우저 캐싱은 강한 캐싱조건부 캐싱(검증) 두 종류로 나눠 이해합니다.

1) 강한 캐싱 (Strong Cache) — 서버에 안 물어봄

캐시가 유효한 동안 네트워크 요청 없이 캐시에서 바로 사용. 가장 빠름.

Cache-Control (주력 헤더)

  • max-age=31536000: 해당 초 동안 신선하다고 간주(1년)
  • no-cache: 캐시는 저장하되 쓰기 전 매번 서버에 검증 (이름과 달리 "캐시 안 함"이 아님)
  • no-store: 아예 저장 안 함(민감 정보)
  • private: 브라우저만 캐시 / public: 중간 서버(CDN)도 캐시 가능
  • immutable: 만료 전엔 재검증조차 하지 말 것

2) 조건부 캐싱 (Revalidation) — 서버에 물어봄

캐시 만료 시 서버에 "바뀌었어?"라고 검증. 안 바뀌었으면 서버는 **본문 없이 304 Not Modified**만 보냄 → 본문 전송 생략으로 트래픽 절약.

  • ETag / If-None-Match (콘텐츠 기반): 서버가 리소스 식별자(ETag)를 주고, 다음 요청 때 If-None-Match로 비교. 같으면 304, 다르면 200 + 새 본문
  • Last-Modified / If-Modified-Since (시간 기반): 마지막 수정 시각으로 비교. 초 단위라 정밀도가 낮아 ETag가 더 정확하고 우선됨

no-cache는 왜 저장하는가?

no-cache는 저장하되 매번 검증합니다. 검증이 성공(304)하면 본문 없이 응답이 오므로 저장본을 그대로 재사용합니다. 만약 저장을 안 했다면 304를 받아도 쓸 게 없어 전체를 다시 받아야 합니다. 즉 no-cache가 절약하는 건 검증 요청이 아니라 무거운 본문 다운로드입니다.

요청 → 저장된 캐시 + ETag 있음
     → 서버에 조건부 요청 (If-None-Match)
        ├─ 안 바뀜 → 304 (본문 없음) → 저장본 재사용 ✅
        └─ 바뀜   → 200 + 새 본문 → 새로 저장

비유: no-cache는 책을 집에 두고(저장) 쓸 때마다 도서관에 "개정판 나왔어요?"라고 전화(검증). 안 나왔으면 집 책을 보고(304), 나왔으면 새로 빌려옴(200). no-store는 매번 통째로 빌려오는 것.

3) 전체 흐름

요청
 ├─ 강한 캐시 유효?(max-age 안 지남)
 │     └─ YES → 캐시 즉시 사용 (네트워크 X, 가장 빠름)
 └─ NO(만료) → 조건부 요청 (If-None-Match / If-Modified-Since)
        ├─ 안 바뀜 → 304 (본문 없음, 캐시 재사용)
        └─ 바뀜   → 200 + 새 본문 + 새 캐시 헤더

4) 실무 전략 — 자산 종류별

  • 정적 자산(JS/CSS/이미지): 파일명에 내용 해시(app.3f9a2b.js) + max-age=31536000, immutable. 내용이 바뀌면 해시(파일명)가 달라져 자동으로 새 파일 인식 → 캐시 버스팅. 영구 캐싱 + 즉시 반영 동시 달성
  • HTML: 어떤 해시 파일을 부를지 알려주는 진입점이라 no-cache로 두고 항상 검증 → 새 해시 파일명을 가리키게 함
  • API 응답: 보통 no-store 또는 짧은 max-age + ETag

정리 표

구분동작헤더결과
강한 캐싱서버에 안 물음Cache-Control: max-age캐시 즉시 사용
조건부 캐싱서버에 검증ETag/Last-Modified304 or 200
캐시 안 함항상 새로 받음no-store200

면접 멘트: "캐싱은 강한 캐싱과 조건부 캐싱으로 나뉩니다. 정적 자산은 파일명 해시 + immutable로 영구 캐싱하면서 변경 시 파일명이 바뀌어 자동 무효화(캐시 버스팅)되게 하고, HTML은 진입점이라 no-cache로 매번 검증합니다. 검증은 ETag가 Last-Modified보다 정확해 우선됩니다."