본문으로 건너뛰기

🌐 브라우저는 어떻게 작동할까?

📖 정의

웹 브라우저는 HTML, CSS, JavaScript 파일을 해석하여 사용자에게 시각적인 웹페이지를 보여주는 소프트웨어입니다. 브라우저는 서버에서 받은 코드를 파싱(parsing)하고, DOM 트리를 생성하며, 스타일을 계산하고, 레이아웃을 구성한 후 화면에 그리는(rendering) 복잡한 과정을 거칩니다.

🎯 비유로 이해하기

레스토랑 주방

브라우저를 레스토랑 주방에 비유하면:

주문서 (HTML)          → 요리사가 읽음
├─ "스테이크"
├─ "샐러드"
└─ "와인"

레시피 (CSS) → 요리 스타일 결정
├─ 스테이크: 미디엄, 소금, 후추
├─ 샐러드: 발사믹 드레싱
└─ 와인: 레드, 차갑게

특별 지시 (JavaScript) → 동적 변경
├─ "손님이 익힘 정도를 바꾸면"
├─ "알레르기 재료 제거"
└─ "추가 주문 가능"

완성된 요리 (Render) → 손님에게 서빙

⚙️ 작동 원리

1. 브라우저의 주요 구성 요소

브라우저
├─ UI (사용자 인터페이스)
│ └─ 주소창, 북마크, 뒤로/앞으로 버튼

├─ 브라우저 엔진
│ └─ UI와 렌더링 엔진 사이 동작 제어

├─ 렌더링 엔진
│ ├─ Blink (Chrome, Edge)
│ ├─ WebKit (Safari)
│ └─ Gecko (Firefox)

├─ JavaScript 엔진
│ ├─ V8 (Chrome, Node.js)
│ ├─ SpiderMonkey (Firefox)
│ └─ JavaScriptCore (Safari)

├─ 네트워킹
│ └─ HTTP 요청/응답 처리

├─ UI 백엔드
│ └─ OS의 UI 메서드 호출

└─ 데이터 저장소
└─ 쿠키, 로컬스토리지, IndexedDB

2. 렌더링 과정 (Critical Rendering Path)

1. HTML 파싱 → DOM 트리 생성
<html>
<body>
<h1>Title</h1>
</body>
</html>

→ DOM Tree:
html
└─ body
└─ h1 ("Title")

2. CSS 파싱 → CSSOM 트리 생성
h1 { color: blue; font-size: 24px; }

→ CSSOM Tree:
h1
├─ color: blue
└─ font-size: 24px

3. JavaScript 실행
- DOM 조작
- 이벤트 처리
- 비동기 작업

4. 렌더 트리 생성 (DOM + CSSOM)
- 화면에 보이는 요소만 포함
- display: none은 제외

5. 레이아웃 (Reflow)
- 각 요소의 정확한 위치와 크기 계산

6. 페인트 (Paint)
- 픽셀을 화면에 그림

7. 합성 (Composite)
- 여러 레이어를 합쳐 최종 화면 생성

💡 실제 예시

HTML 파싱 과정

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css"> <!-- 1. CSS 다운로드 시작 -->
<script src="script.js"></script> <!-- 2. JS 다운로드 및 실행 (차단!) -->
</head>
<body>
<h1>Hello World</h1> <!-- 3. DOM 생성 -->
<script>
// 4. 인라인 스크립트 실행 (차단!)
console.log('Loaded');
</script>
</body>
</html>

<!-- 문제: JavaScript가 HTML 파싱을 차단함 -->

<!-- ✅ 해결: 비동기 로딩 -->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<!-- defer: HTML 파싱 완료 후 실행 -->
<script defer src="script.js"></script>

<!-- async: 다운로드 완료 즉시 실행 -->
<script async src="analytics.js"></script>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>

DOM 조작과 성능

// ❌ 비효율적: 여러 번 reflow 발생
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = i;
document.body.appendChild(div); // 1000번 reflow!
}

// ✅ 효율적: 한 번만 reflow
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = i;
fragment.appendChild(div);
}
document.body.appendChild(fragment); // 1번 reflow

// ✅ 더 좋음: innerHTML (브라우저 최적화)
const html = Array.from({ length: 1000 }, (_, i) =>
`<div>${i}</div>`
).join('');
document.body.innerHTML = html;

Reflow와 Repaint 최소화

// Reflow (레이아웃 재계산 - 비용 높음)
// - 위치, 크기 변경
element.style.width = '100px';
element.style.height = '100px';
element.style.margin = '10px';

// Repaint (다시 그리기 - 비용 낮음)
// - 색상, 배경 변경
element.style.color = 'red';
element.style.backgroundColor = 'blue';

// ❌ 비효율적: 여러 번 reflow
element.style.width = '100px'; // reflow
element.style.height = '100px'; // reflow
element.style.padding = '10px'; // reflow

// ✅ 효율적: 클래스로 한 번에
element.className = 'box'; // 1번 reflow

// CSS
.box {
width: 100px;
height: 100px;
padding: 10px;
}

// ✅ 더 좋음: transform 사용 (reflow 없음)
element.style.transform = 'translateX(100px)'; // GPU 가속
element.style.opacity = 0.5; // GPU 가속

JavaScript 이벤트 루프

console.log('1. 동기 코드 시작');

setTimeout(() => {
console.log('2. Timeout');
}, 0);

Promise.resolve().then(() => {
console.log('3. Promise');
});

console.log('4. 동기 코드 끝');

// 출력 순서:
// 1. 동기 코드 시작
// 4. 동기 코드 끝
// 3. Promise (Microtask Queue)
// 2. Timeout (Task Queue)

// 이벤트 루프 동작
/*
Call Stack (실행 중인 함수)

Microtask Queue (우선순위 높음)
- Promise.then
- MutationObserver

Task Queue (Macrotask)
- setTimeout
- setInterval
- I/O
*/

리소스 로딩 최적화

<!DOCTYPE html>
<html>
<head>
<!-- 1. Preconnect: DNS, TLS 미리 연결 -->
<link rel="preconnect" href="https://fonts.googleapis.com">

<!-- 2. DNS Prefetch: DNS만 미리 조회 -->
<link rel="dns-prefetch" href="https://api.example.com">

<!-- 3. Preload: 중요 리소스 미리 로드 -->
<link rel="preload" href="font.woff2" as="font" crossorigin>

<!-- 4. Prefetch: 나중에 필요한 리소스 미리 로드 -->
<link rel="prefetch" href="next-page.html">

<!-- 5. Critical CSS 인라인 -->
<style>
/* 초기 렌더링에 필요한 CSS */
body { margin: 0; font-family: sans-serif; }
.hero { height: 100vh; }
</style>

<!-- 6. 나머지 CSS는 비동기 로드 -->
<link rel="stylesheet" href="main.css" media="print" onload="this.media='all'">
</head>
<body>
<!-- 7. 이미지 지연 로딩 -->
<img src="hero.jpg" alt="Hero" loading="eager">
<img src="image1.jpg" alt="Image 1" loading="lazy">

<!-- 8. JavaScript는 맨 아래 또는 defer -->
<script defer src="main.js"></script>
</body>
</html>

성능 측정

// Performance API
const perfData = performance.getEntriesByType('navigation')[0];

console.log('DNS 조회:', perfData.domainLookupEnd - perfData.domainLookupStart);
console.log('TCP 연결:', perfData.connectEnd - perfData.connectStart);
console.log('TTFB:', perfData.responseStart - perfData.requestStart);
console.log('DOM 로드:', perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart);
console.log('전체 로드:', perfData.loadEventEnd - perfData.loadEventStart);

// Core Web Vitals 측정
// 1. LCP (Largest Contentful Paint)
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.renderTime || lastEntry.loadTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });

// 2. FID (First Input Delay)
new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
console.log('FID:', entry.processingStart - entry.startTime);
});
}).observe({ type: 'first-input', buffered: true });

// 3. CLS (Cumulative Layout Shift)
let cls = 0;
new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (!entry.hadRecentInput) {
cls += entry.value;
}
});
console.log('CLS:', cls);
}).observe({ type: 'layout-shift', buffered: true });

🤔 자주 묻는 질문

Q1. 브라우저마다 렌더링이 다른 이유는?

A: 각 브라우저가 다른 렌더링 엔진을 사용하기 때문입니다:

Chrome/Edge → Blink
Safari → WebKit
Firefox → Gecko

해결책:
1. 웹 표준 준수
2. 크로스 브라우저 테스트
3. Polyfill 사용
4. CSS Reset/Normalize

Q2. DOM이 무엇인가요?

A: Document Object Model - HTML을 JavaScript로 조작할 수 있는 트리 구조입니다:

<html>
<body>
<h1 id="title">Hello</h1>
<p class="text">World</p>
</body>
</html>
// DOM API로 접근
document.getElementById('title').textContent = 'Hi';
document.querySelector('.text').style.color = 'red';

// DOM 트리
document
└─ html (document.documentElement)
└─ body (document.body)
├─ h1#title
└─ p.text

Q3. Virtual DOM이 더 빠른 이유는?

A: 실제 DOM 조작을 최소화하기 때문입니다:

// ❌ 실제 DOM: 3번 reflow
element.style.width = '100px';
element.style.height = '100px';
element.textContent = 'New';

// ✅ Virtual DOM (React 등)
// 1. 메모리에서 변경사항 계산
// 2. 차이점만 실제 DOM에 적용
// 3. 한 번만 reflow!

// React 예시
function MyComponent() {
const [count, setCount] = useState(0);

// count가 변경되면
// 1. Virtual DOM에서 먼저 비교
// 2. 실제로 변경된 부분만 업데이트
return <div>{count}</div>;
}

Q4. 브라우저 캐시는 어떻게 작동하나요?

A:

HTTP 응답 헤더로 제어:

1. Cache-Control
Cache-Control: max-age=3600 # 1시간 캐시
Cache-Control: no-cache # 매번 검증
Cache-Control: no-store # 캐시 안 함

2. ETag (변경 감지)
응답: ETag: "abc123"
요청: If-None-Match: "abc123"
→ 변경 없으면 304 Not Modified

3. Last-Modified
응답: Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT
요청: If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT
→ 변경 없으면 304
// Service Worker로 캐시 제어
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
// 캐시에 있으면 반환, 없으면 네트워크 요청
return response || fetch(event.request);
})
);
});

Q5. 브라우저 렌더링 최적화 팁은?

A:

// 1. CSS는 head에, JS는 body 끝에
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- content -->
<script src="script.js"></script>
</body>

// 2. 애니메이션은 transform/opacity 사용
// ❌ 느림: width, height, margin (reflow)
element.style.width = '200px';

// ✅ 빠름: transform (GPU 가속)
element.style.transform = 'scaleX(2)';

// 3. requestAnimationFrame 사용
function animate() {
// 애니메이션 코드
requestAnimationFrame(animate); // 60fps에 맞춰 실행
}
requestAnimationFrame(animate);

// 4. Intersection Observer로 지연 로딩
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
}
});
});

document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});

// 5. 디바운스/쓰로틀로 이벤트 최적화
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}

window.addEventListener('resize', debounce(() => {
console.log('Resized!');
}, 250));

🎓 다음 단계

브라우저 작동 원리를 이해했다면, 다음을 학습해보세요:

  1. 웹 성능 최적화 (문서 작성 예정) - 실전 최적화 기법
  2. React란? - Virtual DOM 활용
  3. SEO 기초 (문서 작성 예정) - 검색 엔진 최적화

개발자 도구 활용

// Chrome DevTools 유용한 기능

// 1. Performance 탭
// - Record 버튼으로 성능 측정
// - Lighthouse로 자동 분석

// 2. Network 탭
// - 리소스 로딩 시간 확인
// - Throttling으로 느린 네트워크 시뮬레이션

// 3. Coverage 탭
// - 사용하지 않는 CSS/JS 확인

// 4. Rendering 탭
// - Paint flashing: 다시 그려지는 영역 표시
// - Layout Shift Regions: CLS 원인 파악

🎬 마무리

브라우저는 복잡하지만 최적화된 시스템입니다:

  • 파싱: HTML, CSS를 트리 구조로 변환
  • 렌더링: DOM + CSSOM → 화면에 표시
  • JavaScript 엔진: 코드 실행과 이벤트 처리
  • 최적화: Reflow 최소화, 비동기 로딩, 캐싱

브라우저의 작동 원리를 이해하면 더 빠르고 효율적인 웹사이트를 만들 수 있습니다! 🌐✨