Next.js의 SSR을 공부하다 문득 내가 React의 CSR에 대해서도 제대로 이해하고 있지 않다는 걸 깨달았다. CSR 뿐인가, 빌드나 배포 같은 과정들도 필요하다는 것만 알지 정확히 뭘 하는지는 모른 채 넘어가고 있었다.
이번 기회에 내가 작성한 React 프로젝트 코드가 어떻게 브라우저에서 보이는 웹사이트로 바뀌는지 그 과정을 하나하나 파헤쳐보기로 했다.
React는 대표적인 Client Side Rendering(CSR) 방식으로 동작한다. 서버는 기본 HTML만 전달하고, 실제 UI는 브라우저에서 JavaScript가 실행되면서 그려진다. 여기서 기본 HTML은 index.html
을 가리킨다.
개발 환경에서 index.html
을 보면 이렇게 작성돼 있다. #root
div 요소 하나만 있고, React 앱의 진입점인 main.jsx
가 연결되어 있다.
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
main.jsx
에는 아래와 같은 코드가 있다.
ReactDOM.createRoot(document.getElementById('root')).render(<App />);
조금 더 풀어쓰면 다음과 같다.
const rootElement = document.getElementById('root');
const root = ReactDOM.createRoot(rootElement);
root.render(<App />);
#root
div를 가져온다ReactDOM.createRoot
로 React Root 객체를 생성한다<App />
컴포넌트를 Root에 렌더링한다브라우저가 JS 파일을 읽고 root div에 React Root를 생성하면, 그곳을 기준으로 Virtual DOM이 구성되고 React 컴포넌트들이 실제 DOM에 렌더링된다.
브라우저는 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 트리를 이룬다.
처음에는 <div id="root">
만 존재한다.
React는 Virtual DOM을 기반으로 필요한 요소들을 document.createElement()
와 appendChild()
등으로 실제 DOM에 추가한다.
이때는 상황에 따라 setAttribute
, removeChild
, replaceChild
등 다양한 DOM API가 사용된다.
앞에서 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 번들도 마찬가지다.
이렇게 빌드된 파일들을 사용자에게 제공하는 과정이 배포(deploy)다.
간단히 말해 build(dist) 폴더 전체를 서버에 올리는 것이다.
사용자가 https://example.com
같은 주소로 접속하면, 브라우저는 아래의 순서대로 동작한다.
index.html
을 받는다<script>
와 <link>
를 따라 JS, CSS 번들을 요청한다이런 과정을 거쳐 우리는 React로 만든 앱을 실제 웹사이트에서 실행하고, 브라우저 화면에서 볼 수 있다.
지금까지 React와 Next.js로 이것저것 많이 만들긴 했지만 그 동작 원리를 깊이 들여다본 적은 별로 없었다. 개발 중에 생긴 의문을 해결하려고 공부하긴 했지만, 프로젝트가 끝나면 지식도 같이 휘발되곤 했다. 그러다 보니 기능 구현에는 익숙해졌지만 정작 그 기술을 제대로 활용하고 있는 건지 의문이 들 때가 많았다.
단순히 만들 줄 아는 사람이 아니라 제대로, 잘 만들 줄 아는 개발자가 되기 위해서 지금부터라도 모호한 걸 넘기지 않고, 공부한 건 기록으로 남기는 습관을 들여야겠다.
오, 정말 유익한 내용이에요!