About→

← Studies

브라우저에 URL을 입력하면 무슨 일이 일어날까?

프론트엔드 개발을 하다 보면 React, JavaScript, HTTP, 브라우저 렌더링, 이벤트 루프, Web Worker처럼 다양한 기술을 접하게 된다.

특히 프론트엔드 개발자 면접을 준비하다 보면 이런 개념에 대한 질문을 자주 마주하게 된다.

"브라우저 렌더링 과정은 어떻게 되는가?"
"JavaScript의 이벤트 루프는 어떻게 동작하는가?"
"React는 어떻게 렌더링되는가?"
"Web Worker는 왜 사용하는가?"
"HTTP와 HTTPS의 차이는 무엇인가?"

처음에는 서로 다른 질문처럼 보이지만, 조금 더 큰 관점에서 보면 이 개념들은 서로 독립적으로 존재하지 않는다.

브라우저에 URL을 입력하고 웹페이지를 보는 하나의 과정 안에서 각각의 기술이 서로 다른 위치에서 역할을 하고 있다.

그래서 프론트엔드 면접에서 자주 등장하는 많은 질문들도 이 거시적인 흐름 안에 배치해 보면 서로 연결해서 이해할 수 있다.

결국 중요한 것은 각각의 개념을 단편적으로 암기하는 것이 아니라,

"브라우저에서 웹페이지가 요청되고, 실행되고, 렌더링되고, 사용자와 상호작용하기까지 어떤 일이 일어나는가?"

라는 큰 흐름을 이해하는 것이라고 생각한다.

이 글에서는 이 흐름을 기준으로 프론트엔드 개발에서 자주 접하게 되는 개념들을 하나씩 연결해 보려고 한다.

우리가 매일 하는 행동에서 시작해보자

가장 단순한 상황을 하나 가정해보자.

브라우저를 열고 주소창에 다음과 같이 입력한다.

text
https://yong-hee.com

그리고 Enter를 누른다.

잠시 후 웹페이지가 화면에 나타난다.

사용자 입장에서는 매우 단순한 과정이다.

text
주소 입력
    ↓
Enter
    ↓
웹사이트 표시

하지만 브라우저 내부에서는 이 짧은 순간에 수많은 일이 일어난다.

아주 크게 단순화하면 다음과 같은 흐름으로 볼 수 있다.

text
https://yong-hee.com
          ↓
       URL 해석
          ↓
       DNS 조회
          ↓
    서버와 연결
          ↓
      HTTP 요청
          ↓
      서버 응답
          ↓
       HTML 수신
          ↓
   HTML / CSS / JS 처리
          ↓
       DOM / CSSOM
          ↓
      Render Tree
          ↓
        Layout
          ↓
         Paint
          ↓
       화면 표시
          ↓
     사용자와 상호작용

이것만 보면 단순히 브라우저가 웹페이지를 가져와서 화면에 보여주는 과정처럼 보인다.

그런데 이 흐름 안을 조금씩 들여다보면 우리가 프론트엔드 개발을 하면서 공부했던 거의 모든 개념이 등장하기 시작한다.

1. URL을 입력하면 가장 먼저 무슨 일이 일어날까?

우선 브라우저는 우리가 입력한 URL을 해석해야 한다.

text
https://yong-hee.com

여기에는 여러 정보가 들어 있다.

text
https://
   ↓
프로토콜

yong-hee.com
   ↓
호스트

브라우저는 이 정보를 바탕으로 어디에 어떤 방식으로 요청을 보낼지 결정한다.

그런데 여기서 한 가지 문제가 있다.

우리가 사용하는 것은 yong-hee.com이라는 도메인 이름이지만, 네트워크 통신에서는 서버의 IP 주소가 필요하다.

그래서 등장하는 것이 DNS다.

text
yong-hee.com
      ↓
     DNS
      ↓
   IP 주소

DNS를 통해 도메인 이름에 대응하는 IP 주소를 알아낸 후, 브라우저는 해당 서버와 통신하기 위한 과정을 시작한다.

여기서부터 우리가 면접에서 자주 접하는 질문들이 등장한다.

  • DNS란 무엇인가?
  • DNS 조회는 어떻게 이루어지는가?
  • IP 주소란 무엇인가?
  • TCP는 왜 필요한가?
  • HTTPS는 어떻게 동작하는가?
  • TLS는 왜 필요한가?
  • HTTP/1.1과 HTTP/2는 무엇이 다른가?
  • HTTP/3에서는 무엇이 달라지는가?

처음 보면 전부 별개의 네트워크 질문처럼 보인다.

하지만 실제로는 하나의 질문으로 묶을 수 있다.

"브라우저가 `yong-hee.com`이라는 웹사이트에 요청을 보내기까지 어떤 일이 일어나는가?"

2. 서버와 연결한 다음에는 무엇을 하는가?

브라우저가 서버와 통신할 준비가 되었다면 이제 실제로 웹페이지를 요청한다.

text
Browser
   │
   │ HTTP Request
   ↓
Server
   │
   │ HTTP Response
   ↓
Browser

브라우저가 서버에 요청을 보내고, 서버는 그 요청에 대한 응답을 반환한다.

처음 요청한 웹페이지라면 HTML 문서를 받을 수 있다.

그런데 여기서 끝나지 않는다.

HTML을 분석하다 보면 CSS 파일이나 JavaScript 파일, 이미지, 폰트 등 추가적으로 필요한 리소스가 발견될 수 있다.

결국 하나의 웹페이지를 보는 과정은 하나의 파일을 다운로드하는 과정이 아니다.

text
HTML
 ├── CSS
 ├── JavaScript
 ├── Image
 ├── Font
 └── ...

브라우저는 필요한 리소스를 추가로 요청하고 받아온다.

이 과정에서는 또 다른 질문들이 나온다.

  • HTTP Request와 Response는 무엇인가?
  • HTTP Method는 무엇인가?
  • Status Code는 무엇인가?
  • 브라우저는 리소스를 어떻게 캐싱하는가?
  • CDN은 왜 사용하는가?
  • 서버 응답 시간이 느리면 화면에는 어떤 영향이 생기는가?
  • 네트워크가 느리면 웹페이지는 어떻게 동작하는가?

이 질문들도 결국 같은 흐름에 존재한다.

text
URL
 ↓
네트워크
 ↓
HTTP Request
 ↓
Server
 ↓
HTTP Response
 ↓
Browser

3. HTML을 받으면 바로 화면에 나타나는가?

그렇지 않다.

브라우저가 HTML을 받았다고 해서 HTML 문자열을 그대로 화면에 그리는 것은 아니다.

브라우저는 HTML을 파싱해서 DOM을 만든다.

text
HTML
 ↓
Parsing
 ↓
DOM

예를 들어 다음과 같은 HTML이 있다고 해보자.

html
<div>
  <h1>Hello</h1>
  <p>Frontend Developer</p>
</div>

브라우저는 이것을 다음과 같은 구조로 다룬다.

text
div
├── h1
│   └── "Hello"
└── p
    └── "Frontend Developer"

이것이 DOM이다.

그런데 화면을 만들기 위해서는 HTML 구조만 필요한 것이 아니다.

CSS도 필요하다.

text
HTML
 ↓
DOM

CSS
 ↓
CSSOM

브라우저는 DOM과 CSSOM을 바탕으로 화면에 표시할 요소와 스타일을 결정한다.

그리고 이후 Layout, Paint 등의 과정을 거쳐 실제 화면을 만들어낸다.

text
HTML ──→ DOM ─────┐
                  ├──→ Render Tree
CSS  ──→ CSSOM ───┘
                       ↓
                     Layout
                       ↓
                      Paint
                       ↓
                     화면

여기서 우리가 면접에서 자주 듣는 질문이 등장한다.

"브라우저 렌더링 과정을 설명해보세요."

이 질문을 단순히

text
DOM → CSSOM → Render Tree → Layout → Paint

순서로 외우는 것과,

"브라우저가 서버에서 받은 리소스를 실제 화면으로 바꾸는 과정"

이라고 이해하는 것은 상당히 다르다.

4. 그렇다면 JavaScript는 어디에 있는가?

이제 중요한 질문이 하나 생긴다.

"JavaScript는 이 과정에서 어디에 있는가?"

JavaScript는 브라우저에서 실행된다.

그리고 실행 결과에 따라 DOM을 변경하거나 네트워크 요청을 보내거나 사용자 이벤트를 처리할 수 있다.

text
                 JavaScript
                     ↓
        ┌────────────┼────────────┐
        ↓            ↓            ↓
      DOM 변경    네트워크 요청   이벤트 처리

예를 들어 버튼을 클릭했을 때 화면의 숫자가 변경된다고 생각해보자.

text
사용자 클릭
    ↓
JavaScript 실행
    ↓
상태 / DOM 변경
    ↓
브라우저가 변경된 화면을 다시 처리
    ↓
화면 업데이트

그렇다면 JavaScript는 어떻게 실행되는가?

여기서 우리가 공부했던 개념들이 등장한다.

text
JavaScript
    ↓
Execution Context
    ↓
Call Stack
    ↓
함수 실행

그리고 다음과 같은 질문들이 자연스럽게 연결된다.

  • JavaScript는 왜 싱글 스레드인가?
  • Execution Context란 무엇인가?
  • Lexical Environment란 무엇인가?
  • Hoisting은 왜 발생하는가?
  • Call Stack은 어떻게 동작하는가?
  • Heap은 무엇인가?
  • 함수가 호출되면 Call Stack에서는 어떤 일이 일어나는가?

이제부터는 브라우저의 큰 흐름 안에서 JavaScript의 실행 원리를 볼 수 있다.

5. 그런데 JavaScript는 한 번에 하나만 실행한다는데?

여기서 또 하나의 문제가 생긴다.

JavaScript가 한 번에 하나의 작업을 실행한다면,

네트워크 요청을 보내면서 사용자의 클릭도 처리하고, 타이머도 동작시키는 것처럼 보이는 이유는 무엇일까?

여기서 비동기 처리와 Event Loop가 등장한다.

text
JavaScript
     ↓
  Call Stack
     ↓
Browser가 제공하는 기능
     ↓
Task / Microtask Queue
     ↓
  Event Loop
     ↓
  Call Stack

예를 들어 fetch()를 호출했다고 해보자.

JavaScript가 네트워크 응답이 올 때까지 무작정 기다리는 것이 아니라, 브라우저가 제공하는 네트워크 기능에 작업을 맡기고 이후 결과를 처리할 수 있도록 한다.

그리고 작업이 완료되면 후속 코드가 적절한 큐를 거쳐 다시 JavaScript 실행 환경으로 들어온다.

이 과정에서 우리가 자주 접하는 개념들이 등장한다.

  • Web API
  • Promise
  • Microtask Queue
  • Task Queue
  • Event Loop
  • async/await
  • fetch

이제 Event Loop 역시 별개의 지식이 아니다.

브라우저에서 JavaScript가 비동기 작업을 처리하는 과정의 한 부분이 된다.

6. 그렇다면 React는 어디에 있는가?

이제 우리가 프론트엔드 개발에서 가장 많이 사용하는 React를 이 흐름에 넣어보자.

React는 브라우저와 별개의 환경에서 동작하는 것이 아니다.

결국 JavaScript로 실행되며 브라우저가 제공하는 실행 환경 위에서 동작한다.

text
Browser
   ↓
JavaScript
   ↓
React
   ↓
Component
   ↓
Render
   ↓
DOM 업데이트
   ↓
Browser Rendering
   ↓
화면

예를 들어 React에서 state가 변경되었다고 해보자.

text
State 변경
    ↓
React가 업데이트 필요성을 판단
    ↓
React Render
    ↓
DOM 업데이트
    ↓
Browser Rendering
    ↓
화면 변경

그러면 우리가 React 면접에서 받는 질문들도 이 흐름 안에 들어간다.

  • React는 왜 사용하는가?
  • State가 변경되면 무슨 일이 일어나는가?
  • Re-render란 무엇인가?
  • Virtual DOM은 무엇인가?
  • React.memo는 무엇을 해결하는가?
  • useMemo, useCallback은 언제 사용하는가?
  • React.lazy는 왜 사용하는가?
  • Suspense는 무엇을 하는가?

결국 React도 브라우저에서 JavaScript가 실행되고 화면이 변경되는 전체 과정의 일부라고 볼 수 있다.

7. 화면이 느리다면 어디를 봐야 하는가?

이제 이 전체 흐름을 성능 문제에 적용할 수 있다.

웹사이트가 느리다고 해보자.

단순히

"React가 느린 것 같다."

라고 생각할 필요는 없다.

전체 과정을 따라가면서 어느 단계에서 문제가 발생했는지 확인할 수 있다.

text
URL
 ↓
DNS
 ↓
Connection
 ↓
HTTP Request
 ↓
Server Response
 ↓
Resource Download
 ↓
HTML / CSS / JS Parsing
 ↓
JavaScript Execution
 ↓
Rendering
 ↓
Paint
 ↓
Interaction

예를 들어,

text
DNS가 오래 걸린다
→ 네트워크 / DNS 문제

서버 응답이 늦다
→ 서버 처리 / TTFB 문제

JavaScript 파일이 너무 크다
→ 다운로드 / 파싱 비용

JavaScript 실행이 오래 걸린다
→ Main Thread 작업량

렌더링 계산이 무겁다
→ Layout / Paint 비용

메모리가 계속 증가한다
→ Heap / GC / Memory Leak 가능성

이렇게 보면 성능 최적화도 각각의 기술을 외워서 적용하는 것이 아니라,

"전체 과정에서 어느 단계가 병목인가?"

를 찾는 문제로 바뀐다.

8. 여기서 Web Worker와 Cache도 연결된다

예를 들어 JavaScript에서 아주 무거운 작업을 수행한다고 해보자.

JavaScript가 메인 스레드에서 오랜 시간 실행되면 사용자 입력이나 렌더링에 영향을 줄 수 있다.

이때 Web Worker를 사용해 별도의 스레드에서 작업을 수행하도록 분리할 수 있다.

text
Main Thread
 ├── UI
 ├── JavaScript
 └── Rendering

        ↕ message

Web Worker
 └── 무거운 연산

캐시도 마찬가지다.

이미 받아온 리소스를 다시 네트워크에서 받아야 한다면 불필요한 시간이 발생할 수 있다.

브라우저의 HTTP Cache나 Service Worker Cache 등을 활용하면 특정 리소스를 다시 사용하는 과정에서 네트워크 비용을 줄일 수 있다.

즉,

text
Web Worker
→ JavaScript 처리 과정의 병목을 분리

Cache
→ 네트워크 요청과 데이터 전달 비용을 줄임

처럼 각각의 기술이 전체 흐름에서 특정 문제를 해결한다.

9. 결국 하나의 흐름으로 연결된다

지금까지 살펴본 내용을 하나의 그림으로 합쳐보면 다음과 같다.

text
사용자
  │
  │ https://yong-hee.com
  ↓
┌─────────────────────┐
│        Browser      │
└─────────────────────┘
          ↓
        URL 해석
          ↓
         DNS
          ↓
   Connection / TLS
          ↓
     HTTP Request
          ↓
        Server
          ↓
    HTTP Response
          ↓
    HTML / CSS / JS
          ↓
   ┌──────┼──────┐
   ↓      ↓      ↓
  DOM    CSSOM  JavaScript
   │             │
   │        ┌────┴─────┐
   │        ↓          ↓
   │    Call Stack   Event Loop
   │                   ↓
   │             Async / Promise
   │
   └──────┬────────────┘
          ↓
      React 실행
          ↓
    Component / State
          ↓
       Render
          ↓
    DOM 업데이트
          ↓
   Browser Rendering
          ↓
   Layout → Paint
          ↓
         화면
          ↓
    사용자 Interaction
          ↓
       다시 JavaScript
          ↓
        반복

그리고 성능 문제가 발생하면 이 전체 흐름을 다시 살펴본다.

text
네트워크 문제인가?
        ↓
서버 응답 문제인가?
        ↓
리소스가 너무 큰가?
        ↓
JavaScript가 너무 무거운가?
        ↓
Main Thread가 막혔는가?
        ↓
Rendering 비용이 큰가?
        ↓
Memory 문제가 있는가?

이렇게 하나의 흐름으로 바라보면 처음에는 서로 달라 보였던 개념들이 조금씩 연결되기 시작한다.

마치며

프론트엔드 개발을 공부하다 보면 수많은 개념을 만나게 된다.

DNS, HTTP, TCP, TLS, DOM, CSSOM, JavaScript, Execution Context, Call Stack, Event Loop, Promise, React, Web Worker, Cache, Rendering 등 처음에는 서로 전혀 다른 기술처럼 보인다.

하지만 이들을 하나씩 따로 외우기보다,

"사용자가 브라우저에 URL을 입력한 순간부터 웹페이지가 화면에 나타나고, 이후 사용자와 상호작용하기까지 어떤 일이 일어나는가?"

라는 하나의 흐름 위에 배치해 보면 각각의 개념이 어디에서 필요한지 이해하기 쉬워진다.

그리고 프론트엔드 면접에서 만나는 질문들도 이 흐름 안에서 다시 바라볼 수 있다.

브라우저 렌더링에 대한 질문은 화면이 만들어지는 과정에 대한 질문이고, Event Loop에 대한 질문은 JavaScript 실행과 비동기 처리에 대한 질문이며, React에 대한 질문은 JavaScript와 UI 업데이트를 어떻게 관리하는지에 대한 질문이다.

결국 각각의 질문에 대한 답을 따로 암기하는 것보다,

웹페이지 하나가 만들어지고 동작하는 전체 과정을 이해하는 것이 프론트엔드의 여러 개념을 연결해서 이해하는 출발점이 될 수 있다.

이제부터는 이 큰 흐름을 기준으로 각각의 과정을 하나씩 자세히 살펴보려고 한다.