

현대 웹 브라우저는 복잡한 소프트웨어입니다. 사용자가 URL을 입력하거나 링크를 클릭하면 브라우저는 네트워크 요청을 보내고, 응답으로 받은 HTML/CSS/JS 등을 해석하여 화면에 렌더링합니다. 이 과정에서 DOM, CSSOM, JavaScript 엔진, 이벤트 루프 등 다양한 요소가 유기적으로 작동합니다.

브라우저는 내부적으로 여러 프로세스로 구성된 멀티 프로세스 아키텍처를 사용합니다. 하나의 브라우저 창/탭이 여러 프로세스로 나뉘어 동작하며, 각 프로세스는 서로 격리되어 안정성과 보안을 높입니다.
예를 들어 Chrome 등의 현대 브라우저는 보통 다음과 같은 프로세스로 구성됩니다:
브라우저 프로세스: UI 처리 (주소창, 북마크, 뒤로/앞으로 버튼 등)와 네트워킹, 파일 접근 등의 브라우저 전반 기능을 담당. 사용자 입력 처리와 다른 프로세스 간 조율을 담당합니다.
렌더러 프로세스: 각 탭의 웹 콘텐츠(HTML, CSS, JS 등)를 파싱하고 렌더링하여 화면에 표시하는 역할입니다 . 탭마다 별도의 렌더러 프로세스를 가질 수 있고, 이 프로세스는 Sandbox 로 격리되어 직접적인 시스템 자원 접근이 제한됩니다.
🔐 샌드박스(Sandbox)란?
샌드박스는 외부에서 들어온 프로그램이나 코드가 제한된 환경에서 실행되도록 하여, 시스템 전체에 영향을 주지 않도록 하는 보안 메커니즘입니다. 이러한 격리된 환경에서는 프로그램이 시스템 자원에 직접 접근하는 것이 제한되며, 다른 프로그램이나 데이터에 영향을 미칠 수 없습니다.
🧪 개발자에게 미치는 영향
- CORS 정책: 샌드박스 환경에서는 다른 출처의 리소스에 대한 접근이 제한되므로, Cross-Origin Resource Sharing(CORS) 정책을 준수해야 합니다.
- iframe 샌드박스 속성: HTML의
<iframe>요소에서 sandbox 속성을 사용하면, 해당 프레임의 콘텐츠를 샌드박스 환경에서 실행할 수 있습니다. 이를 통해 스크립트 실행, 폼 제출, 플러그인 사용 등을 제한할 수 있습니다.- 웹 보안 강화: 샌드박스를 활용하면, 외부에서 가져온 콘텐츠나 서드파티 스크립트의 실행을 제한하여 웹 애플리케이션의 보안을 강화할 수 있습니다.
이 외에도 브라우저에 따라 네트워크 프로세스, 오디오 프로세스, 확장 기능 프로세스 등 여러 가지 보조 프로세스가 있을 수 있습니다. 이러한 분할 덕분에 한 탭이 죽어도 전체 브라우저가 죽지 않고, 보안적으로도 렌더러 프로세스를 제한하여 악성 웹 페이지가 시스템에 접근하지 못하게 합니다.
단, 프로세스가 많아지면 메모리 사용은 증가할 수 있어 브라우저는 기기 성능에 따라 프로세스 개수를 조절합니다.
🧠 브라우저의 프로세스 조절 방식
- 하드웨어 사양 감지: 브라우저는 시스템의 CPU 코어 수, 메모리 용량 등을 감지하여, 동시에 실행할 수 있는 프로세스 수를 결정합니다.
- 프로세스 제한: 예를 들어, Chrome은 시스템 자원에 따라 생성할 수 있는 프로세스 수를 제한하며, 이 한도에 도달하면 동일한 사이트나 탭을 동일한 프로세스에서 실행하여 자원 사용을 최적화합니다 .
- 동일 사이트 그룹화: 브라우저는 동일한 사이트의 탭이나 iframe을 하나의 프로세스로 그룹화하여 실행함으로써, 불필요한 프로세스 생성을 방지하고 자원 사용을 줄입니다.
- 프로세스 재사용: 새로운 탭을 열 때 기존에 생성된 프로세스를 재사용하여, 메모리 사용량을 최소화합니다.
브라우저는 네트워크-→ 파싱-→ 스타일 및 레이아웃-→ 그리기의 4단계 큰 흐름으로 페이지를 화면에 올립니다.
| 단계 | 과정 | 세부 작업 |
|---|---|---|
| 1. 네트워크 요청 | URL → DNS → TCP/TLS → HTTP 응답 받기 | HTML·CSS·JS·이미지 다운로드 |
| 2. 파싱 | HTML → DOM 트리, CSS → CSSOM 트리 | 스크립트·스타일은 리소스 우선 로드 및 파서-차단 규칙 적용 |
| 3. 렌더 트리 & 레이아웃 | DOM + CSSOM → 렌더 트리, 각 노드의 위치·크기 계산 | ‘Reflow(레이아웃)’ 단계로 화면 좌표 확정 |
| 4. 페인트 & 컴포지트 | 스타일을 픽셀로 칠한 뒤 여러 레이어를 합성 | GPU-가속으로 Paint → Composite → 모니터 출력 |
이제 각 단계별로 브라우저에서 어떤 일이 일어나는지 살펴봅시다
사용자가 웹 브라우저에 URL을 입력하거나 링크를 클릭하면, 브라우저는 원하는 웹 페이지를 가져오기 위해 여러 단계의 네트워크 과정을 거칩니다. 여기에는 DNS 조회, 서버와의 TCP 연결 설정, HTTP 요청/응답 교환 등이 포함되며, 이러한 과정을 통해 최종적으로 서버의 응답을 받아 웹 페이지를 구성하게 됩니다.
브라우저가 URL을 입력받으면 가장 먼저 해당 URL의 도메인 이름을 해석합니다.
예를 들어 https://example.com/page을 요청하면, 브라우저는 우선 example.com이라는 도메인에 대응되는 서버의 IP 주소를 알아내야 합니다.
이때, DNS 조회를 통해 도메인 이름을 IP 주소로 변환하며, 이 과정에서 브라우저나 운영체제가 이전에 조회한 결과를 캐시하고 있다면 캐시된 값을 재사용하여 속도를 높입니다. IP 주소를 얻었다면, 브라우저는 해당 IP의 서버와 TCP 연결을 맺습니다 (3-way 핸드셰이크 과정을 통해 연결 수립). 만약 URL이 https://로 시작하는 HTTPS라면, TCP 연결 후 추가로 TLS 보안 핸드셰이크를 수행하여 통신 암호화를 설정합니다.
DNS와 DNS 캐싱
![]()
✅ DNS란?
사람이 기억하기 쉬운 도메인 주소 (www.example.com)를 컴퓨터가 이해하는 IP 주소 (93.184.216.34)로 변환하는 역할을 합니다.❓ 왜 DNS 조회가 느릴 수 있을까?
DNS 조회는 네트워크 왕복 요청이 필요합니다.
특히 도메인을 처음 접속할 때,로컬 캐시에도, ISP 캐시에도 없으면 루트 DNS 서버, TLD 서버, 권한 있는 서버를 단계별로 거쳐야 합니다.
이 과정은 수십~수백 밀리초가 걸릴 수 있고, 페이지 로딩 시간에 영향을 줍니다.✅ DNS 캐싱이란?
DNS 캐싱은 이전에 조회한 도메인의 IP 주소를 임시 저장해두고, 다음에 같은 도메인을 요청할 때 빠르게 바로 사용하는 기술입니다.
TCP 연결 (3-way Handshake) 과 TLS 핸드셰이크
![]()
브라우저는 얻은 IP와 포트(기본 443 HTTPS)에 대해 TCP 연결을 시도합니다. TCP는 신뢰성 있는 연결을 위해 3-way 핸드셰이크(SYN, SYN-ACK, ACK) 과정을 거칩니다 . 이 단계에서 클라이언트와 서버는 서로를 확인하고 패킷 전송을 준비합니다.
URL이 HTTPS라면 추가로 TLS/SSL 핸드셰이크를 수행합니다. 클라이언트가 “ClientHello”를 보내 암호화 방식 등을 협상하고, 서버는 인증서 등을 보내어 신뢰성을 검증받습니다. 이 과정은 몇 차례 메시지 왕복이 필요하지만, 보안을 위해 필수입니다.
연결이 수립되면 브라우저는 서버에 HTTP 요청을 보냅니다.
이 요청에는 원하는 리소스의 경로 (/page 등), HTTP 메서드(일반적으로 페이지 요청은 GET), 그리고 사용자 에이전트 정보나 쿠키 등의 헤더들이 포함됩니다.
서버는 이 요청을 받고 해당 리소스를 찾아 HTTP 응답을 돌려줍니다. 응답은 상태 코드, 응답 헤더들과 함께 HTML 문서 콘텐츠를 포함합니다.
브라우저는 응답 헤더를 확인하여 전송된 콘텐츠의 종류나 인코딩을 파악하고, 본문을 해석하기 시작합니다. 이때 응답이 리다이렉트(3xx 상태 코드)라면 브라우저는 자동으로 새 위치로 다시 요청을 보내는 과정을 거칩니다.
여기서는 HTTP 요청에 관한 내용만 짚겠습니다.
HTML 문서의 내용을 수신하면서, 브라우저는 곧장 렌더링 엔진에 내용을 전달하여 파싱을 시작합니다. 또한 HTML 안에 추가로 가져와야 할 리소스들(예: CSS, JS, 이미지)이 링크되어 있다면, 브라우저는 병렬로 여러 네트워크 요청을 보냅니다.
HTTP/1.1 환경에서는 한 번에 최대 6개 정도의 TCP 연결을 활용해 동시 다운로드를 수행하며, HTTP/2 이상에서는 하나의 연결로 여러 리소스를 동시에 주고받는 멀티플렉싱을 활용합니다.
브라우저는 이렇게 받은 리소스들을 적절히 캐싱하여, 동일한 자원을 재요청할 때 캐시 검증을 통해 변경되지 않았다면 네트워크 대신 캐시에서 로드하여 성능을 높입니다. 👩💻
🚀 네트워크 성능과 HTTP 프로토콜의 발전
과거 HTTP/1.1 시절에는 브라우저가 동시에 보낼 수 있는 요청 개수에 제한이 있어, 일반적으로 각 도메인당 최대 6개 연결까지 병렬 요청을 처리했습니다. 이 때문에 더 많은 리소스를 병렬로 받아오기 위해 일부 사이트는 도메인을 분산시키는 도메인 샤딩 기법을 쓰기도 했습니다.
HTTP/2 프로토콜이 도입되면서 이러한 제약이 크게 완화되었는데, 하나의 TCP 연결만으로도 여러 요청을 동시에 주고받을 수 있는 멀티플렉싱이 가능해졌습니다. 이를 통해 연결 수를 늘리지 않아도 병렬 전송 효과를 얻을 수 있어 페이지 로딩 성능이 향상되었습니다.
최신 HTTP/3에서는 TCP 대신 UDP 기반의 QUIC 프로토콜을 사용하여 전송 계층의 지연을 줄이고, 패킷 손실 시 개별 스트림만 재전송하는 방식으로 더 빠르고 안정적인 통신을 제공합니다. 이러한 프로토콜 발전 덕분에 브라우저 네트워크 성능은 지속적으로 개선되고 있습니다.
한편, 현대 웹 앱에서는 서비스 워커(Service Worker) 를 이용해 네트워크 요청을 가로채고 캐시된 응답을 제공함으로써 오프라인 동작을 지원하기도 합니다.
서비스 워커는 브라우저와 네트워크 사이에 위치한 프록시처럼 동작하여, 네트워크 연결이 불안정하거나 끊긴 경우에도 미리 캐시된 자원을 제공합니다. 이는 별도의 백그라운드 스레드에서 동작하며, 애플리케이션이 지정한 정책에 따라 요청을 가로채거나 변경할 수 있어 고급 기능(푸시 알림, 백그라운드 동기화 등)을 구현하는 데 활용됩니다.
브라우저가 서버로부터 HTML 문서를 받으면, 렌더링 엔진이 이 문서를 해석(파싱)하여 화면에 표시하는 과정을 진행합니다.
렌더링 엔진은 일반적으로 DOM 생성 → 렌더 트리 구성 → 레이아웃 → 페인팅의 단계를 거쳐 동작합니다. 먼저 HTML을 해석해 DOM 트리를 만들고, CSS 규칙을 적용해 렌더 트리를 구성합니다. 이후 렌더 트리의 각 요소를 어디에 배치할지 계산하는 레이아웃 과정을 거치며, 마지막으로 완성된 렌더 트리를 화면 픽셀로 변환하여 페인팅 단계에서 그려줍니다. 이러한 파이프라인을 통해 사용자가 요청한 웹 페이지가 시각적으로 완성됩니다. 각 단계는 순차적으로 이뤄지지만, 브라우저는 사용자 경험을 위해 가능한 한 병렬 처리와 최적화를 수행하여 빠르게 화면에 내용을 표시합니다.
브라우저의 렌더링 엔진은 전달받은 HTML 텍스트를 읽어들이며 곧바로 파싱(parsing) 을 시작합니다.
파싱이란 텍스트 형식으로 된 문서를 구조화된 객체 모델로 변환하는 작업인데, HTML의 파싱 결과물로 DOM(Document Object Model) 트리가 구축됩니다. HTML 내의 각 태그는 DOM 트리의 하나의 노드(node) 로 변환되어 계층 구조를 이룹니다.
동시에 렌더링 엔진은 HTML 안에 포함된 스타일 정보도 처리하는데, <style> 태그나 별도로 링크된 CSS 파일을 로드하여 CSSOM(CSS Object Model) 트리를 생성합니다. 이렇게 만들어진 DOM 트리와 CSSOM 트리를 조합하여 화면에 그릴 요소만 모아놓은 렌더 트리(Render Tree) 를 구성합니다. 렌더 트리의 각 노드는 화면에 표시될 하나의 상자(box) 요소이며, 스타일이 적용된 시각적 속성(색상, 폰트 크기 등)을 가지고 있습니다.
렌더 트리가 완성되면 브라우저는 레이아웃 단계에서 각 요소의 위치와 크기를 계산합니다.
이를 위해 뷰포트(브라우저 화면 영역) 내에서 부모-자식 요소 관계와 CSS 박스 모델을 고려해 좌표를 산출합니다. 이 과정은 Chromium 계열 엔진에서는 “레이아웃”, Gecko 엔진(Firefox)에서는 “리플로우(Reflow)“라고 불리며, 명칭만 다를 뿐 하는 일은 유사합니다.
레이아웃이 끝나면 곧바로 페인팅 단계가 진행되어, 계산된 렌더 트리의 각 노드에 대해 화면에 픽셀을 칠합니다. 브라우저는 OS의 그래픽 API를 사용하여 폰트 글리프를 그리거나 색상을 채우고 이미지를 출력하며, 이렇게 최종 화면이 완성됩니다.
이 전체 렌더링 과정은 순차적으로 보이지만 실제로는 상당히 점진적으로 일어납니다. 브라우저는 HTML 문서를 모두 받기를 기다리지 않고, 도착하는 대로 콘텐츠를 조금씩 파싱하여 렌더링을 시작합니다. 예를 들어 초기 <head> 부분의 스타일 정보를 받으면 바로 CSS 파싱을 시작하고, <body>의 일부 내용이 도착하면 이를 DOM으로 만들어 배치한 뒤 화면에 그려 사용자에게 점진적으로 결과를 보여줍니다. 나중에 추가 내용이 도착하면 그 부분을 이어서 처리하고 렌더링을 갱신하는 식으로 동작합니다. 이러한 스트리밍 렌더링 덕분에 사용자 입장에서는 빈 화면에서 한꺼번에 렌더링되는 것보다 빠르게 콘텐츠 일부라도 확인할 수 있어 체감 성능이 향상됩니다 🙂.
브라우저의 렌더링 과정을 이해하고 있다면, 이를 활용하여 렌더링 성능을 최적화할 수 있습니다. DOM 요소를 동적으로 추가하거나 스타일을 변경할 때, 무분별하게 수행하면 매번 레이아웃과 페인팅이 반복 발생하여 성능이 떨어집니다. 이를 막기 위해 한번에 변경할 것들은 모아서 처리하는 것이 좋습니다.
예를 들어, 100개의 요소 스타일을 바꿀 때 한꺼번에 클래스명을 변경하면 한 번의 레이아웃만 발생하지만, 100번에 걸쳐 개별 요소 스타일을 수정하면 100회의 레이아웃 연산이 발생할 수 있습니다. 이러한 불필요한 레거시 레이아웃 작업을 줄이는 것을 “리플로우 최소화“ 라고 하며, 성능 튜닝의 중요한 요소입니다.
또, 레이아웃 스레싱(layout thrashing) 을 피하는 것이 중요한데, 이는 자바스크립트에서 DOM 정보를 읽는 함수(예: element.offsetHeight)를 호출한 직후 바로 스타일을 변경하는 동작을 빈번히 교차해서 수행할 경우 발생합니다. 이때 브라우저는 최신 레이아웃 정보를 알려주기 위해 강제로 즉각 레이아웃을 실행하므로, 이러한 코드 패턴은 피하는 것이 좋습니다.
한편, CSS 속성 선택도 성능에 영향을 줄 수 있습니다. 예를 들어 단순한 위치 이동이나 투명도 변화에는 top이나 left 대신 transform이나 opacity 속성을 활용하면 페인팅이나 레이아웃 없이 GPU 합성 단계만으로 처리가 가능하여 더욱 부드러운 애니메이션을 구현할 수 있습니다.
이러한 기법은 브라우저의 컴포지터(합성) 레이어를 효율적으로 활용하는 것으로, 복잡한 스타일 변경보다는 합성 단계에서 처리할 수 있는 변경을 적용함으로써 Reflow와 Repaint를 최소화하는 원리입니다.
복잡해 보이지만, 결국 브라우저는 “리소스를 가져오고 → 해석하고 → 그리며 → 반응한다”는 단순한 루틴 안에서 복잡한 최적화를 수행합니다.