본문 바로가기
📌 Front End/└ React

[React] 리액트 CSR 렌더링 및 빌드 배포 가이드 - React Vite 프로젝트 CSR

by 쫄리_ 2024. 10. 4.
728x90
반응형

📌 CSR(Client Side Rendering)

CSR은 클라이언트 측에서 UI를 그리는 방식으로,

처음에는 서버에서 빈 HTML 파일을 브라우저에 전달합니다.

이후 자바스크립트가 실행되면서 클라이언트에서 UI가 렌더링됩니다.
React와 같은 라이브러리는 자바스크립트가 실행되기 전까지 화면을 렌더링할 수 없기 때문에

사용자는 처음에 잠시 동안 빈 화면을 보게 됩니다.

 

🔎 CSR 과정 요약

  1. 사용자가 브라우저를 통해 서버에 접속합니다.
  2. 서버는 자바스크립트 코드가 연결된 빈 HTML 파일을 브라우저로 전달합니다.
  3. 브라우저는 자바스크립트 파일을 다운로드한 후 실행합니다.
  4. 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
    }
});

 


728x90
반응형