브라우저 주소창에 URL을 입력하면 화면이 뜨기까지: DNS부터 렌더링까지
빠른 답
- 주소 입력 뒤 바로 화면이 뜨는 것이 아니라 DNS 조회, 연결 수립, TLS 협상, HTTP 요청, 렌더링이 순서대로 진행됩니다.
- 첫 화면 속도는 서버 응답 시간만이 아니라 CSS 다운로드, JavaScript 실행, 레이아웃·페인트 비용에도 크게 영향을 받습니다.
- HTTPS 사이트는 요청 전에 인증서 검증과 키 협상이 들어가므로 TLS 단계가 별도 병목이 될 수 있습니다.
- 병목은 Chrome DevTools의 Network와 Performance 탭으로 네트워크 지연과 렌더링 비용을 나눠 확인하는 것이 가장 빠릅니다.
목차
흐름으로 보기
이 여섯 단계는 웹 페이지 로딩의 가장 큰 뼈대입니다. 앞쪽은 리소스를 받아오는 네트워크 흐름이고, 뒤쪽은 받은 데이터를 실제 화면으로 바꾸는 브라우저 렌더링 흐름입니다. 페이지가 느릴 때 원인을 정확히 찾으려면 이 둘을 섞지 말고 끊어서 봐야 합니다.
왜 이 과정을 같이 봐야 하나
브라우저 주소창에 https://www.google.com을 입력하는 행동은 단순히 “서버에 요청한다”로 끝나지 않습니다. 브라우저는 먼저 어디로 연결할지 찾아야 하고, 안전한 연결을 만들어야 하며, HTML과 정적 자원을 받아온 뒤 그것을 다시 화면용 구조로 바꿔야 합니다. 사용자가 보는 첫 화면은 서버 응답 그 자체가 아니라, 브라우저가 수행한 렌더링 파이프라인의 결과물입니다.
이 관점을 놓치면 성능 문제를 잘못 짚기 쉽습니다. 예를 들어 서버는 HTML을 빨리 내려줬는데도 화면이 늦게 뜰 수 있습니다. CSS가 늦게 와서 스타일 계산이 밀렸거나, 동기 JavaScript가 HTML 파싱을 막았거나, 레이아웃과 페인트 비용이 커졌을 수 있기 때문입니다. 그래서 이 주제는 네트워크와 렌더링을 따로 외우기보다 하나의 흐름으로 이해하는 편이 훨씬 실용적입니다.
연결이 열리기 전: DNS, TCP, TLS, HTTP
브라우저는 먼저 입력값이 검색어인지 URL인지 판단합니다. URL이라고 판단하면 도메인 이름을 IP 주소로 바꾸기 위해 DNS 조회를 시작합니다. 이때 브라우저 캐시, 운영체제 캐시, 로컬 네트워크 캐시, DNS 서버 캐시를 순서대로 활용할 수 있습니다. 캐시에 있으면 빠르게 끝나고, 없으면 추가 질의가 발생합니다.
IP를 얻은 뒤에는 서버와 연결을 만듭니다. HTTP/1.1과 HTTP/2는 기본적으로 TCP 위에서 동작하므로 먼저 3-way handshake가 필요합니다. HTTPS라면 여기에 TLS 핸드셰이크가 추가됩니다. 이 단계에서는 서버 인증서를 검증하고, 통신에 사용할 암호화 키를 협상합니다. 사용자 입장에서는 보이지 않지만, 네트워크 RTT가 크거나 인증서 검증 비용이 커지면 여기서 지연이 생길 수 있습니다.
연결이 준비되면 브라우저는 HTTP 요청을 보내고, 보통 가장 먼저 HTML 문서를 받습니다. 중요한 점은 브라우저가 HTML을 전부 받은 뒤 움직이는 것이 아니라, 받는 동안 파싱을 시작한다는 사실입니다. 그래서 HTML 안에서 CSS, JavaScript, 이미지, 폰트 같은 자원을 발견하면 후속 요청이 바로 이어질 수 있습니다.
간단한 분해 측정은 curl만으로도 어느 정도 확인할 수 있습니다.
curl -o /dev/null -s \
-w "dns:%{time_namelookup}\nconnect:%{time_connect}\ntls:%{time_appconnect}\nttfb:%{time_starttransfer}\ntotal:%{time_total}\n" \
https://www.google.com
이 출력에서 dns, connect, tls, ttfb, total을 나눠 보면 네트워크 쪽 병목이 어디에 있는지 대략 감이 옵니다. 예를 들어 ttfb는 짧은데 브라우저 첫 화면이 늦다면, 서버보다 렌더링 비용을 먼저 의심하는 편이 맞습니다.
응답이 화면이 되는 핵심: DOM, CSSOM, 렌더 트리
HTML 응답이 도착하면 브라우저는 파서를 돌려 DOM을 만듭니다. DOM은 문서 구조를 메모리 안의 트리로 표현한 것입니다. 제목, 문단, 버튼, 리스트 같은 요소들이 부모-자식 관계로 연결됩니다. 이 단계는 “문서 구조를 이해하는 단계”라고 보면 됩니다.
CSS 파일이 도착하면 브라우저는 CSSOM을 만듭니다. CSSOM은 어떤 선택자에 어떤 스타일 규칙이 적용되는지를 정리한 스타일 구조입니다. DOM만으로는 화면을 그릴 수 없고, CSSOM까지 있어야 각 요소의 실제 모습과 계산 기준이 정해집니다. 그래서 CSS는 초기 렌더링 속도에 직접적인 영향을 줍니다.
이후 브라우저는 DOM과 CSSOM을 결합해 렌더 트리를 만듭니다. 여기에는 실제 화면에 표시할 노드만 들어갑니다. 문서 구조 전체를 담는 DOM과 달리, 렌더 트리는 “그릴 대상”에 더 가깝습니다. 예를 들어 화면에 표시되지 않는 일부 요소는 DOM에는 있어도 렌더 트리에는 빠질 수 있습니다.
이 지점에서 자주 나오는 오해가 있습니다. “HTML만 받으면 거의 다 끝난 것 아닌가?”라는 생각입니다. 실제로는 그렇지 않습니다. CSSOM이 준비되지 않으면 스타일 계산을 완료할 수 없고, 스타일이 확정되지 않으면 레이아웃도 안정적으로 진행되지 않습니다. 첫 화면 속도에서 CSS가 중요한 이유가 바로 여기에 있습니다.
레이아웃, 페인트, 컴포지팅은 각각 무엇을 하나
렌더 트리가 준비되면 브라우저는 각 요소의 위치와 크기를 계산합니다. 이 단계가 레이아웃입니다. 박스 모델, 줄바꿈, 폰트 크기, 부모 폭, flex, grid, 스크롤 영역 같은 정보가 여기서 반영됩니다. 요소 크기나 문서 구조가 바뀌면 레이아웃이 다시 일어날 수 있습니다.
레이아웃 다음은 페인트입니다. 이 단계에서는 텍스트 색상, 배경, 테두리, 그림자, 이미지 같은 시각 정보를 실제 그리기 명령으로 바꿉니다. 요소 수가 많거나 스타일 효과가 복잡하면 페인트 비용이 커질 수 있습니다. 특히 큰 배경 이미지, 블러, 복잡한 그림자는 생각보다 비용이 큽니다.
마지막이 컴포지팅입니다. 브라우저는 요소를 여러 레이어로 나누고, 필요한 레이어만 합성해 최종 화면을 만듭니다. transform이나 opacity 같은 속성은 레이아웃 전체를 다시 계산하지 않고 컴포지팅 단계에서 처리되는 경우가 많아 애니메이션 최적화에 자주 활용됩니다. 다만 레이어를 무작정 늘리는 것이 항상 좋은 것은 아니고, 메모리 사용량과 관리 비용도 함께 봐야 합니다.
실무에서는 “변경이 어느 단계까지 전파되는가”를 보는 습관이 중요합니다.
- 박스 크기나 텍스트 내용 변경은 레이아웃까지 다시 갈 수 있습니다.
- 배경색 변경은 보통 페인트만 다시 일으킵니다.
transform기반 이동은 컴포지팅에서 끝날 가능성이 높습니다.
이 차이를 이해하면 어떤 UI 변경은 부드럽고, 어떤 변경은 스크롤 중 끊기는지 설명하기 쉬워집니다.
첫 화면을 늦추는 흔한 비용
첫 화면을 늦추는 대표적인 비용은 렌더링 차단 CSS와 동기 JavaScript입니다. CSS는 스타일 계산에 필요하므로 늦게 도착하면 화면 구성이 밀립니다. 동기 스크립트는 HTML 파싱을 멈출 수 있고, 실행 중 DOM을 바꾸면 이후 레이아웃과 페인트 비용도 커집니다. 그래서 “HTML 응답이 빨랐다”는 사실만으로는 체감 속도를 설명할 수 없습니다.
또 하나 흔한 문제는 강제 동기 레이아웃입니다. 레이아웃 값을 읽은 직후 스타일을 바꾸는 패턴을 반복하면 브라우저는 중간 계산을 계속 강제로 수행해야 할 수 있습니다. 흔히 layout thrashing이라고 부르는 문제입니다.
다음 코드는 같은 루프 안에서 레이아웃 값을 읽고 스타일을 바로 쓰는 예입니다.
const cards = document.querySelectorAll('.card');
for (const card of cards) {
const width = card.getBoundingClientRect().width;
card.style.height = `${width * 0.6}px`;
}
목록이 작을 때는 티가 잘 안 나지만, 요소 수가 많아지면 레이아웃 계산이 반복되어 메인 스레드 점유 시간이 길어질 수 있습니다. 읽기와 쓰기를 분리하면 비용을 좀 더 예측 가능하게 만들 수 있습니다.
const cards = [...document.querySelectorAll('.card')];
const widths = cards.map((card) => card.getBoundingClientRect().width);
requestAnimationFrame(() => {
cards.forEach((card, index) => {
card.style.height = `${widths[index] * 0.6}px`;
});
});
이 패턴이 모든 상황의 정답은 아니지만, 브라우저 렌더링 비용을 이해하는 데는 좋은 예시입니다. 핵심은 “DOM을 바꿨다”가 아니라 “그 변경이 레이아웃, 페인트, 컴포지팅 중 어디까지 다시 일으키는가”입니다.
DevTools로 병목을 어떻게 찾나
이 주제를 공부할 때 가장 실용적인 도구는 Chrome DevTools입니다. Network 탭은 요청 단위 병목을 보여 줍니다. DNS, 연결, SSL, 대기, 다운로드 시간을 나눠 볼 수 있고, 어떤 리소스가 늦게 왔는지, 캐시가 적용됐는지, 리다이렉트가 있었는지 확인하기 쉽습니다.
반면 Performance 탭은 브라우저 내부 작업을 보여 줍니다. 긴 JavaScript 실행, 스타일 재계산, 레이아웃, 페인트, 컴포지팅, 메인 스레드 Long Task가 어느 시점에 발생했는지를 볼 수 있습니다. 즉, Network는 “받아오는 비용”, Performance는 “받은 뒤 처리하는 비용”을 보는 도구에 가깝습니다.
실전에서는 아래 순서가 효율적입니다.
Network에서 HTML, CSS, JS의 도착 시점과 우선순위를 먼저 본다.Performance에서 긴 Task, 잦은 Layout, 큰 Paint 구간을 확인한다.Coverage로 초기 로딩에 실제로 쓰는 CSS·JS 비율을 점검한다.Disable cache와 CPU/Network throttling을 켜고 다시 재현해 본다.
Lighthouse 점수는 요약 지표로는 유용하지만, 실제 원인을 찾을 때는 타임라인과 요청 waterfall을 직접 읽는 편이 더 정확합니다.
구성과 설정으로 줄일 수 있는 비용
브라우저 렌더링은 프런트 코드만으로 최적화되지 않습니다. 서버와 CDN 설정도 큰 영향을 줍니다. 대표적인 것이 캐시 정책과 압축입니다. CSS, JS, 폰트처럼 자주 바뀌지 않는 파일은 긴 캐시를 걸고, 파일명에 해시를 넣는 구성이 일반적입니다.
다음은 정적 자원 캐시와 압축을 함께 고려한 예시입니다.
location /assets/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
gzip on;
gzip_types text/css application/javascript application/json text/plain;
gzip_min_length 1024;
이 설정은 재방문 비용을 줄이고 전송량도 줄여 줍니다. 다만 HTML 문서까지 무조건 오래 캐시하면 배포 직후 갱신이 늦어질 수 있으므로, 문서와 정적 자산을 같은 정책으로 다루면 안 됩니다. 보통 HTML은 짧게, 해시가 붙은 정적 파일은 길게 캐시합니다.
스크립트 로딩 전략도 중요합니다. 모든 스크립트를 초기에 동기 실행하면 HTML 파싱과 렌더링이 쉽게 지연됩니다. 초기 상호작용에 꼭 필요한 코드만 앞에 두고, 나머지는 defer, 코드 스플리팅, 지연 로딩을 활용하는 편이 낫습니다. 목표는 “자바스크립트를 무조건 줄이기”가 아니라 “첫 화면과 직접 관련 없는 실행을 뒤로 미루기”입니다.
면접과 실무에서 자주 틀리는 설명
가장 흔한 실수는 네트워크와 렌더링을 하나로 뭉개서 설명하는 것입니다. DNS -> TCP -> HTTP까지만 말하면 절반만 설명한 셈입니다. 사용자가 체감하는 “화면이 떴다”는 순간은 DOM, CSSOM, 렌더 트리, 레이아웃, 페인트, 컴포지팅까지 이어진 뒤에야 만들어지기 때문입니다.
두 번째 실수는 JavaScript를 항상 “렌더링 이후의 후처리”처럼 설명하는 것입니다. 동기 스크립트는 HTML 파싱을 멈출 수 있고, DOM과 스타일 계산에도 직접 영향을 줍니다. 즉, JavaScript는 렌더링 뒤에 따로 붙는 것이 아니라 렌더링 파이프라인 자체에 개입할 수 있습니다.
세 번째 실수는 서버 응답만 빠르면 첫 화면도 자동으로 빠르다고 생각하는 것입니다. 실제로는 CSS가 늦거나, 폰트가 늦거나, 메인 스레드가 바쁘면 사용자는 빈 화면이나 불완전한 화면을 더 오래 보게 됩니다. 그래서 성능 문제는 서버, 네트워크, 브라우저 렌더링을 단계별로 나눠서 봐야 합니다.
면접 답변으로는 다음 흐름이면 충분히 탄탄합니다. 브라우저가 URL을 해석하고 DNS로 IP를 찾은 뒤 TCP와 TLS로 연결을 준비합니다. 그다음 HTTP 요청으로 HTML을 받고, HTML과 CSS를 파싱해 DOM과 CSSOM을 만든 뒤 렌더 트리, 레이아웃, 페인트, 컴포지팅을 거쳐 화면을 그린다고 설명하면 됩니다. 마지막에 “병목은 DevTools의 Network와 Performance 탭으로 나눠 확인한다”까지 붙이면 실무 감각도 드러납니다.
원문 참고
https://www.maeil-mail.kr/question/20
댓글
댓글 쓰기