📌 CSR(Client Side Rendering)
CSR은 클라이언트 측에서 UI를 그리는 방식으로,
처음에는 서버에서 빈 HTML 파일을 브라우저에 전달합니다.
이후 자바스크립트가 실행되면서 클라이언트에서 UI가 렌더링됩니다.
React와 같은 라이브러리는 자바스크립트가 실행되기 전까지 화면을 렌더링할 수 없기 때문에
사용자는 처음에 잠시 동안 빈 화면을 보게 됩니다.
🔎 CSR 과정 요약
- 사용자가 브라우저를 통해 서버에 접속합니다.
- 서버는 자바스크립트 코드가 연결된 빈 HTML 파일을 브라우저로 전달합니다.
- 브라우저는 자바스크립트 파일을 다운로드한 후 실행합니다.
- React 코드가 실행되면서 UI가 화면에 렌더링되고, 사용자에게 최종 화면이 보여집니다.
🔎 CSR로 React 앱 번들링
개발 중에는 Vite가 코드 변화를 실시간으로 처리하지만,
배포할 때는 최적화된 파일로 묶는 번들링 과정이 필요합니다.
번들링은 React 코드를 하나의 JavaScript 파일로 압축하여 서버에 배포하는 작업입니다.
🔎 빌드 과정
npm run build 명령어를 실행하면, 다음과 같은 파일들이 생성됩니다.
이 파일들은 배포 서버에서 사용자에게 제공됩니다.
- JS 파일 : 번들된 리액트 코드
- HTML 파일 : 페이지의 기본 구조를 담당하는 파일
- CSS 파일 : 스타일 정보를 담은 파일
🔎 Babel과 Vite
Babel은 리액트 코드를 브라우저에서 이해할 수 있는 자바스크립트로 변환하는 도구입니다.
Vite는 이 변환 작업을 도와 main.tsx 파일을 빌드하고, 이를 index.html 파일과 연결합니다.
배포 후, 서버는 이 번들된 파일들을 사용자에게 제공합니다.
✏️ React 앱 클라이언트에서 렌더링되는 구조
main.tsx는 React 앱이 실행되고, index.html에 있는 #reactRoot에 React가 렌더링됩니다.
브라우저는 이 파일을 받아서, React 코드를 실행해 사용자가 볼 수 있는 UI를 만듭니다.
📁 components/📜main.tsx
// ▶ 📁components/📜main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './app';
// React가 클라이언트에서 페이지를 렌더링하는 부분
const root = ReactDOM.createRoot(document.querySelector("#reactRoot") as HTMLElement);
root.render(<App />);
📄 index.html
// ▶ 📄index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Vite & React CSR</title>
</head>
<body>
<div id="reactRoot"></div> <!-- 빈 HTML 구조 -->
<script type="module" src="components/main.tsx"></script> <!-- JS 파일 로드 -->
</body>
</html>
📌 React로 클라이언트 측 렌더링(CSR)
CSR(Client Side Rendering)은 기본적으로 React의 자동 번들링 과정입니다.
브라우저의 개발자 도구에서 "Network" 탭을 열고 "index.html"의 Preview를 보면,
초기에는 내용이 비어 있는 것을 확인할 수 있습니다.
이러한 CSR 방식으로 페이지를 로드할 때, 초기 페이지가 빈 상태로 시작하기 때문에
SEO(검색 엔진 최적화)에 불리합니다.
하지만 아주 저렴한 서버에서 간단히 페이지를 보여주는 것이 목표라면 이 방식으로도 충분합니다.
CSR의 빌드와 배포 과정을 다음과 같이 설명할 수 있습니다.
✅ Express 라이브러리 설치
CSR 방식으로 리액트 앱을 배포할 때는 서버에서 정적 파일을 제공해야 합니다.
이를 위해 다음 라이브러리들을 설치합니다.
- express : 서버에서 리액트 앱의 정적 파일(HTML, JS, CSS)을 제공하는 역할
- fs : 파일 시스템을 다루기 위한 Node.js의 기본 모듈 (파일을 읽고 쓰는 작업에 사용)
npm i express fs
📜 Express 서버 설정 (server.js)
'📜server.js' 새 파일 생성
root(최상위) 경로에 'server.js' 새 파일을 만든 후, 아래와 같이 코드를 작성합니다.
이 코드는 Express라는 도구를 사용하여 웹 서버를 만드는 내용입니다.
Express 불러오기 | import express from 'express'; Express를 사용하기 위해 가져옵니다. |
서버 앱 만들기 | const app = express(); 웹 서버를 만듭니다. |
정적 파일 제공 | app.use('/', express.static('dist/client')); 사용자가 웹사이트에 접속할 때, dist/client 폴더에 있는 파일들을 보여줍니다. |
서버 실행 | app.listen(9999); 서버를 시작합니다. http://localhost:9999 웹사이트에 접속할 수 있습니다. |
// ▶ 📜server.js
import express from 'express';
const app = express();
app.use('/', express.static('dist/client')); // 서버가 정적 파일을 제공하도록 설정
app.listen(9999);
✅ Vite 빌드 명령어
리액트 앱을 배포하기 전에 Vite를 사용해 코드를 빌드해야 합니다.
빌드를 통해 최적화된 파일을 생성할 수 있습니다.
- --outDir : 빌드된 파일이 저장될 폴더 경로를 지정 (예: 📁dist/client 폴더에 결과물이 저장됨)
npx vite build --outDir dist/client
📜 Express 서버 실행 단축어 설정 (package.json)
'📜package.json' ➡️ scripts 카테고리에 단축어 추가
"📜package.json" 파일은 프로젝트의 설정과 의존성을 관리하는 중요한 파일입니다.
Express 서버를 실행하기 위해, scripts 카테고리에 다음과 같은 단축어를 추가합니다.
"server": "node server.js",
scripts 카테고리에
"server": "node server.js", 옵션 추가한다.
📜 package.json
// ▶ 📜package.json
{
"name": "project",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"start": "npx vite dev",
"build": "npx vite optimize & npx vite build",
"server": "node server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"express": "^4.21.0",
"fs": "^0.0.1-security",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.5.4",
"vite": "^5.4.0"
}
}
🎯 서버 실행 명령어
"📜package.json" 파일의 scripts 카테고리에 서버 실행 단축어를 설정했으므로,
아래의 명령어로 서버를 실행합니다.
기존에 실행 중인 리액트 서버가 있다면 종료한 후 실행합니다.
npm run server
🌐 브라우저 실행
서버가 클라이언트에 "📜index.html" 파일을 보내면,
초기에는 내용이 비어 있는 HTML이 전송됩니다.
그 후, 애플리케이션에 연결된 JavaScript 파일들을 서버에서 다운로드합니다.
즉, 화면이 표시되긴 하지만, 계속해서 파일을 다운로드하고 있는 상태입니다.
브라우저의 개발자 도구에서 'Network' 탭을 열고 'index.html'의 미리보기를 확인하면,
초기에는 내용이 비어 있는 것을 확인할 수 있습니다.
http://localhost:9999/index.html
✏️ 클라이언트 사이드 렌더링(CSR) 예시 코드
🗂️ 폴더 구조
.code/day27
├── components
│ ├── app.tsx
│ └── main.tsx
├── dist
│ └── client
│ ├── assets
│ │ └── index-CgUitWUd.js
│ └── index.html
├── server.js
├── index.html
├── package.json
├── tsconfig.json
└── vite.config.ts
파일명 | 설명 |
📁 components/app.tsx | 애플리케이션 메인 컴포넌트 |
📁 components/main.tsx | React 앱의 진입점 ReactDOM을 사용하여 앱을 렌더링 |
💻 dist/client/assets/index-CgUitWUd.js | Vite로 빌드된 자바스크립트 파일 클라이언트에서 애플리케이션 로직을 실행 |
💻 dist/client/index.html | 클라이언트에 제공되는 HTML 파일 애플리케이션의 뼈대를 형성 초기 내용은 비어 있습니다. |
🗄️ server.js | Express 서버의 설정 및 실행을 담당, 클라이언트 요청을 처리 사용자가 접속 시 빌드된 dist/client 폴더의 index.html 실행 |
📄 index.html | 기본 HTML 템플릿 파일로 프로젝트의 초기 HTML 구조 정의 |
⚙️ package.json | 프로젝트 설정 및 의존성 관리 Express 서버 실행을 위한 단축어를 scripts 카테고리에 추가 |
⚙️ tsconfig.json | TypeScript 설정 |
⚙️ vite.config.ts | Vite 빌드 도구 설정 |
📁 components/app.tsx
import React from 'react';
export default function (){
return <table>
<tbody>
{
Array.from({ length: 10 }).map((v,idx) => (
<tr key={idx}>
<td>데이터</td>
<td>데이터</td>
<td>데이터</td>
<td>데이터</td>
<td>데이터</td>
<td>데이터</td>
<td>데이터</td>
<td>데이터</td>
<td>데이터</td>
<td>데이터</td>
</tr>
))
}
</tbody>
</table>
}
📁 components/main.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './app';
const root = ReactDOM.createRoot(document.querySelector("#reactRoot") as HTMLElement);
root.render(
<App/>
)
💻 dist/client/assets/index-CgUitWUd.js
(function(){const n=document.createElement("link").relList;if(n&&n.supports&&n.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))r(l);new MutationObserver(l=>{for(const u of l)if(u.type==="childList")for(const o of u.addedNodes)o.tagName==="LINK"&&o.rel==="modulepreload"&&r(o)}).observe(document,{childList:!0,subtree:!0});function t(l){const u={};return l.integrity&&(u.integrity=l.integrity),l.referrerPolicy&&(u.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?u.credentials="include":l.crossOrigin==="anonymous"?u.credentials="omit":u.credentials="same-origin",u}function r(l){if(l.ep)return;l.ep=!0;const u=t(l);fetch(l.href,u)}})();var Bi={exports:{}},br={},Hi={exports:{}},L={};/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/*
app.tsx와 같은 파일이 포함되어 있으며,
이는 실제 React 컴포넌트 코드가 포함된 파일을 나타냅니다.
*/
checkDCE(ec)}catch(e){console.error(e)}}ec(),es.exports=ge;var zd=es.exports,Vi=zd;Wl.createRoot=Vi.createRoot,Wl.hydrateRoot=Vi.hydrateRoot;function Ld(){return se.jsx("table",{children:se.jsx("tbody",{children:Array.from({length:10}).map((e,n)=>se.jsxs("tr",{children:[se.jsx("td",{children:"데이터"}),se.jsx("td",{children:"데이터"}),se.jsx("td",{children:"데이터"}),se.jsx("td",{children:"데이터"}),se.jsx("td",{children:"데이터"}),se.jsx("td",{children:"데이터"}),se.jsx("td",{children:"데이터"}),se.jsx("td",{children:"데이터"}),se.jsx("td",{children:"데이터"}),se.jsx("td",{children:"데이터"})]},n))})})}const Td=Wl.createRoot(document.querySelector("#reactRoot"));Td.render(se.jsx(Ld,{}));
💻 dist/client/index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Vite & React CSR</title>
<script type="module" crossorigin src="/assets/index-CgUitWUd.js"></script>
</head>
<body>
<div id="reactRoot"></div>
</body>
</html>
🗄️ server.js
import express from 'express';
const app = express();
app.use('/', express.static('dist/client'));
app.listen(9999);
📄 index.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Vite & React CSR</title>
</head>
<body>
<div id="reactRoot"></div>
<script type="module" src="components/main.tsx"></script>
</body>
</html>
⚙️ package.json
{
"name": "project",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"start": "npx vite dev",
"build": "npx vite optimize & npx vite build",
"server": "node server.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"dependencies": {
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.1",
"express": "^4.21.0",
"fs": "^0.0.1-security",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.5.4",
"vite": "^5.4.0"
}
}
⚙️ tsconfig.json
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "react",
"module": "ESNext",
"target": "ESNext",
"moduleResolution": "Node",
"esModuleInterop": true,
"resolveJsonModule": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true
}
}
⚙️ vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
root: "./",
base: "/",
build: {
outDir: "./dist"
},
server: {
port: 9999,
open: true
}
});
'📌 Front End > └ React' 카테고리의 다른 글
[React] 리액트에서 Nivo Chart 데이터 시각화 (3) | 2024.10.06 |
---|---|
[React] 리액트 SSR 렌더링 및 빌드 배포 가이드 - React Vite 프로젝트 SSR (3) | 2024.10.04 |
[React] 웹 렌더링 방식(CSR, SSR, SSG) (4) | 2024.10.02 |
[React] Chakra UI 테마(Theme) - 사용자 정의 스타일 (0) | 2024.09.29 |
[React] Chakra UI 이벤트 컴포넌트, Chakra UI 훅 - Accordion, Tabs, Modal, Drawer, Popover, useDisclosure 훅 (2) | 2024.09.29 |