React는 어떻게 브라우저에 표시될까? (CSR 과정 이해하기)

sohyoeun·2025년 6월 8일
1
post-thumbnail

Next.js의 SSR을 공부하다 문득 내가 React의 CSR에 대해서도 제대로 이해하고 있지 않다는 걸 깨달았다. CSR 뿐인가, 빌드나 배포 같은 과정들도 필요하다는 것만 알지 정확히 뭘 하는지는 모른 채 넘어가고 있었다.

이번 기회에 내가 작성한 React 프로젝트 코드가 어떻게 브라우저에서 보이는 웹사이트로 바뀌는지 그 과정을 하나하나 파헤쳐보기로 했다.

React 앱은 브라우저에 어떻게 보여질까?

React는 대표적인 Client Side Rendering(CSR) 방식으로 동작한다. 서버는 기본 HTML만 전달하고, 실제 UI는 브라우저에서 JavaScript가 실행되면서 그려진다. 여기서 기본 HTML은 index.html을 가리킨다.

1. 기본 HTML: index.html

개발 환경에서 index.html을 보면 이렇게 작성돼 있다. #root div 요소 하나만 있고, React 앱의 진입점인 main.jsx가 연결되어 있다.

<body>
  <div id="root"></div>
  <script type="module" src="/src/main.jsx"></script>
</body>

2. 진입점: main.jsx

main.jsx에는 아래와 같은 코드가 있다.

ReactDOM.createRoot(document.getElementById('root')).render(<App />);

조금 더 풀어쓰면 다음과 같다.

const rootElement = document.getElementById('root');
const root = ReactDOM.createRoot(rootElement);
root.render(<App />);
  1. HTML의 #root div를 가져온다
  2. ReactDOM.createRoot로 React Root 객체를 생성한다
  3. <App /> 컴포넌트를 Root에 렌더링한다

브라우저가 JS 파일을 읽고 root div에 React Root를 생성하면, 그곳을 기준으로 Virtual DOM이 구성되고 React 컴포넌트들이 실제 DOM에 렌더링된다.

JSX는 어떻게 처리될까?

브라우저는 React에서 사용하는 JSX 문법을 이해하지 못한다. 그래서 JSX는 컴파일 타임에 자동으로 React.createElement()로 변환된다.

예를 들어:

<h1 className="title">Hello</h1>

⬇️ 이렇게 바뀐다:

React.createElement("h1", { className: "title" }, "Hello");

이렇게 만들어진 React Element 객체는 이렇게 생겼다:

{
  type: 'h1',
  props: {
    className: 'title',
    children: 'Hello'
  }
}

이 React Element는 Virtual DOM의 최소 단위이고, 이런 객체들이 모여 Virtual DOM 트리를 이룬다.

Virtual DOM은 어떻게 실제 DOM에 반영될까?

1. 초기 렌더링

처음에는 <div id="root">만 존재한다.
React는 Virtual DOM을 기반으로 필요한 요소들을 document.createElement()appendChild() 등으로 실제 DOM에 추가한다.

2. 재렌더링 (state 변경 등)

  1. 상태(state) 변경 → 컴포넌트 재실행 → 새로운 Virtual DOM 생성
  2. 이전 Virtual DOM과 비교
  3. 바뀐 부분만 실제 DOM에 최소한의 조작으로 반영

이때는 상황에 따라 setAttribute, removeChild, replaceChild 등 다양한 DOM API가 사용된다.

React 프로젝트는 어떻게 실행 가능한 파일로 바뀔까?

앞에서 React가 브라우저에서 어떻게 렌더링되는지 살펴봤다.
그런데 우리가 개발하면서 보는 React 코드는 브라우저가 바로 실행할 수 있는 형태가 아니다.
이걸 실제로 실행 가능한 형태로 바꾸려면 빌드(build) 과정이 필요하다.

빌드란 JSX, TypeScript, 모듈화된 컴포넌트 등 우리가 작성한 모든 개발용 코드를 브라우저가 이해할 수 있도록 JS, CSS, HTML 등으로 변환하는 과정이다. 이 결과는 보통 build/ 또는 dist/ 폴더에 저장된다.

예를 들어, React + Vite 프로젝트에서 npx vite build를 실행하면 dist/ 폴더가 생성되고, 그 안에는 다음과 같은 파일들이 들어있다:

  • index.html: 기본 HTML 파일
  • index-xxxxx.js: 모든 JS 코드를 합쳐놓은 JS 번들
  • index-xxxxx.css: 모든 CSS를 합쳐놓은 CSS 번들

그리고 index.html은 이렇게 생겼다.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Vite + React</title>
    <script type="module" crossorigin src="/assets/index-CXRVFtHk.js"></script>
    <link rel="stylesheet" crossorigin href="/assets/index-Cw6CGQpQ.css">
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>

JS 번들은 프로젝트의 모든 JS 코드와 의존성을 하나의 실행 가능한 파일로 압축한 것이고, CSS 번들도 마찬가지다.

React 앱은 어떻게 실제 웹에 올라갈까?

이렇게 빌드된 파일들을 사용자에게 제공하는 과정이 배포(deploy)다.

간단히 말해 build(dist) 폴더 전체를 서버에 올리는 것이다.

사용자가 https://example.com 같은 주소로 접속하면, 브라우저는 아래의 순서대로 동작한다.

  1. 서버로부터 index.html을 받는다
  2. HTML 안의 <script><link>를 따라 JS, CSS 번들을 요청한다
  3. JS가 실행되며 React 앱이 브라우저 안에서 실행된다

이런 과정을 거쳐 우리는 React로 만든 앱을 실제 웹사이트에서 실행하고, 브라우저 화면에서 볼 수 있다.


지금까지 React와 Next.js로 이것저것 많이 만들긴 했지만 그 동작 원리를 깊이 들여다본 적은 별로 없었다. 개발 중에 생긴 의문을 해결하려고 공부하긴 했지만, 프로젝트가 끝나면 지식도 같이 휘발되곤 했다. 그러다 보니 기능 구현에는 익숙해졌지만 정작 그 기술을 제대로 활용하고 있는 건지 의문이 들 때가 많았다.

단순히 만들 줄 아는 사람이 아니라 제대로, 잘 만들 줄 아는 개발자가 되기 위해서 지금부터라도 모호한 걸 넘기지 않고, 공부한 건 기록으로 남기는 습관을 들여야겠다.

2개의 댓글

comment-user-thumbnail
2025년 6월 8일

오, 정말 유익한 내용이에요!

1개의 답글