서버사이드 리액트 가볍게 시작해보기

신은수·2023년 8월 25일
0

ReactJS

목록 보기
10/13

1. CSR과 SSR

1) CSR

CSR(클라이언트 사이드 렌더링)은 브라우저에서 JavaScript를 통해 동적으로 페이지를 생성하는 방식

  1. 껍데기 뿐인 html 파일을 다운로드
  2. bundle.js 다운로드 및 실행
  3. 추가 데이터(users.json)를 다운로드하여 렌더링

2) SSR

SSR(서버사이드 렌더링)은 웹 페이지의 초기 로딩 시 서버에서 HTML을 생성하여 클라이언트로 전달하는 방식

  1. 서버에서 index.html을 렌더링
  2. 클라이언트는 index.html을 다운로드하여 화면에 띄움
  3. bundle.js를 다운로드하여 앱을 살아있는 상태로 만듬

3) CSR과 SSR 비교

CSRSSR
초기 로딩 속도자바스크립트 파일까지 모두 다운로드 받은 후에 화면이 띄워지기 때문에 초기 로딩 속도가 느리다.브라우저는 서버로부터 받은 렌더링 준비가 완료된 HTML 파일을 화면에 바로 띄우기 때문에 초기 로딩 속도가 빠르다.
이후 구동 속도초기 로딩 시 필요한 모든 파일이 연결되어 있기므로 구동 속도가 빠르다.화면을 띄운 후 자바스크립트 로직을 연결하기 때문에 사용 가능한 시점이 뒤늦다.
조작 가능 시점사용자가 페이지를 볼 수 있음과 동시에 입력 및 조작이 가능하다.사용자가 페이지를 볼 수는 있지만 조작은 불가능한 로딩 시간이 존재한다.
SEO빈 HTML 파일이 전달되어 SEO에 취약하다.데이터가 포함된 렌더링 준비된 HTML 파일이 전달되어 SEO에 유리하다.
서버 자원 사용클라이언트가 연산, 라우팅 등을 직접 처리하여 서버의 부담이 적다.매번 서버에 로딩을 요청해야 하기 때문에 서버 부하가 크다.
보안쿠키 외에 사용자 정보를 저장할 공간이 마땅치 않아 보안에 취약할 수 있다.렌더링 시, 서버에서의 필터링이나 이스케이핑 등 선조치를 통해 보안 공격에 대응할 수 있다.


2. 서버사이드 리액트란?

1) React의 동작 방식(CSR)

npx create-react-app my-react-app 명령어로 React 앱을 만들어서 npm run start 을 실행한다. 그리고 개발자 도구에서 Network 탭을 열어, localhost:3000 에 진입했을 때 Webpack의 개발용 서버가 어떤 파일들을 응답해주는지 확인해보자

이 중에서 응답받은 파일 중 제일 처음에 있는 HTML 파일 부터 살펴보면
응답 받은 HTML 을 살펴보면 root 라는 id를 가지고 있는 div 태그 안에는 비어있는 것을 볼 수 있다. 네트워크 응답 화면에서 HTML 파일 아래에 bundle.js 라는 javascript 파일도 받았는데, 이 javascript 파일을 통해 element 들을 그리게 된다. 이것이 CSR의 동작하는 원리이다.

2) 서버사이드 리액트란?

위에서 리액트는 CSR인 것을 볼 수 있는데 서버사이드 리액트는 말그대로 서버사이드에서 리액트가 렌더링이 되도록 하는 것을 말한다. 서버사이드 리액트 같은 경우 완성된 html을 html파일로 보내지 않고 string으로 보낸다.

SSR에서 백엔드 개발환경은 api서버와 렌더링 서버로 나뉜다. 2가지로 나누는 이유는 서버 부하를 막기위함이다.

나는 렌더링 서버를 express로 구현하였다.


3. 서버사이드 리액트 시작하기

1) 시작 단계

  • 폴더 생성 후 터미널에 npm init
  • 생성된 package.json에 아래와 같이 복사 붙여넣기 한 후 npm install로 패키지 다운로드
    {
      "name": "react-server-side-rendering",
      "version": "1.0.0",
      "description": "react server side rendering project",
      "main": "index.js",
      "scripts": {},
      "author": "",
      "license": "ISC",
      "dependencies": {
        "axios": "0.16.2",
        "babel-cli": "6.26.0",
        "babel-core": "6.26.0",
        "babel-loader": "7.1.2",
        "babel-preset-env": "1.6.0",
        "babel-preset-es2015": "6.24.1",
        "babel-preset-es2017": "6.24.1",
        "babel-preset-react": "6.24.1",
        "babel-preset-stage-0": "6.24.1",
        "compression": "1.7.0",
        "concurrently": "3.5.0",
        "express": "4.15.4",
        "express-http-proxy": "1.0.6",
        "lodash": "4.17.4",
        "nodemon": "1.12.0",
        "npm-run-all": "4.1.1",
        "react": "16.0.0",
        "react-dom": "16.0.0",
        "react-helmet": "5.2.0",
        "react-redux": "5.0.6",
        "react-router-config": "1.0.0-beta.4",
        "react-router-dom": "4.2.2",
        "redux": "3.7.2",
        "redux-thunk": "2.2.0",
        "serialize-javascript": "1.4.0",
        "webpack": "3.5.6",
        "webpack-dev-server": "2.8.2",
        "webpack-merge": "4.1.0",
        "webpack-node-externals": "1.6.0"
      }
    }

2) index.js 생성 및 Home컴포넌트 생성

  • index.js

    const express = require("express");
    const app = express();
    const React = require("react");
    const renderToString = require("react-dom/server").renderToString;
    const Home = require("./components/Home").default;
    
    app.get("/", (req, res) => {
        const content = renderToString(<Home />); // renderToString은 string으로 바꾸는 메서드
        res.send(content);
    
    }); // 사용자가 루트 주소로 요청하면 콜백함수를 실행한다.
    
    app.listen(3000, () => {
      console.log("3000번 포트가 열렸습니다!");
    }); // 서버가 포트를 연다.
  • Home.js

    import React from "react";
     const Home = () => {
     return (
       <div>
         <h1>this is Eunsu!!</h1>
       </div>
     );
    };
    export default Home;
    ``

3) Webpack 설정

  • 클라이언트사이드와 마찬가지로 서버사이드도 jsx를 해석하는 과정이 필요하다. jsx문법이 nodejs 환경이 이해하는 문법으로 바뀌어야하고 이를 webpack과 babel이 한다.
    따라서 webpack.server.js 파일 생성해서 webpack 설정을 해줘야한다.

  • build 폴더 생성

  • webpack.server.js

    const path = require("path");
    
    module.exports = {
      target: "node", // 웹팩이 빌드할 때 노드환경에서 돌아가도록 빌드해야하는 구나를 알게됨.
    
      entry: path.resolve("./src/index.js"),
    
      output: {
        filename: "bundle.js",
        path: path.resolve("./build"),
      },
    
      module: {
        rules: [
          {
            test: /\.js?$/,
            loader: "babel-loader",
            exclude: /node_modules/,
            options: {
              presets: [
                "react",
                "stage-0", // 최신 js문법도 사용할 수 있도록 만들어주는 옵션
                ["env", { target: { browsers: ["last 2 versions"] } }], // babel을 어떤 환경에서 구동시킬 것인가. 최신 브라우저에서 2버전 전까지도 지원하겠다. 라는 뜻
              ],
            },
          },
        ],
      },
    };

4) 구동시키기

  • package.json 파일에 scripts내부에 "build-server": "webpack --config webpack.server.js" 추가
    => webpack.server.js파일을 참고하여 webpack으로 build해라.
  • npm run build-server 명령어 터미널에 입력
  • node build/bundle.js 터미널에 치면, localhost:3000에 Home 컴포넌트가 string 형태로 온 것을 볼 수 있다.

5) 추가 설정

  • 파일에 추가 수정 내용이 있을 때 마다 npm run build 명령어와 node build/bundle.js를 치는 것은 번거롭기 때문에 package.json파일에 아래와 같은 명령어 들을 추가한다.
    "scripts": {
        "build-server": "webpack --config webpack.server.js --watch",
        "start-server": "nodemon --watch build --exec node build/bundle.js"
      },
    • webpack --config webpack.server.js --watch: 수정사항 생길 때마다 자동으로 빌드해라
    • nodemon --watch build --exec node build/bundle.js: build폴더를 감시하고 수정사항이 생기면, node build/bundle.js 명령어를 실행시켜라

4. 이벤트가 실행이 안돼?

  • 만약에 Home Component를 아래와 같이 수정하면 어떻게 동작할까?
    import React from "react";
    const Home = () => {
      return (
        <div>
          <h1>this is Eunsu!!</h1>
          <button
            onClick={() => {
              console.log("clicked");
            }}
          >
            버튼입니다
          </button>
        </div>
      );
    };
    export default Home;

  • 콘솔창에 아무것도 찍히지 않는 것을 볼 수 있다
  • network 탭을 열어보면, console.log('clicked')라는 JavaScript가 전달되지 않은 것을 확인 할 수 있다.

2) 두개의 bundle.js가 필요하다

  • 첫번째 webpack은 JSX를 nodejs 에서 해석할 수 있게 하였다.
  • 두번째 webpack은 브라우저에서 동작하는 로직들이 담겨있는 번들을 만들어야 한다.

3) webpack.client.js 설정

  • public 폴더 생성

  • webpack.client.js

    const path = require("path");
    
    module.exports = {
      entry: path.resolve("./src/client.js"),
    
      output: {
        filename: "bundle.js",
        path: path.resolve("./public"),
      },
    
      module: {
        rules: [
          {
            test: /\.js?$/,
            loader: "babel-loader",
            exclude: /node_modules/,
            options: {
              presets: [
                "react",
                "stage-0", // 최신 js문법도 사용할 수 있도록 만들어주는 옵션
                ["env", { target: { browsers: ["last 2 versions"] } }], // babel을 어떤 환경에서 구동시킬 것인가. 최신 브라우저에서 2버전 전까지도 지원하겠다. 라는 뜻
              ],
            },
          },
        ],
      },
    };

4) package.json 추가 설정

"scripts": {
 	...
    "build-client": "webpack --config webpack.client.js --watch"
  },
  ...

5) client.js 생성

import React from "react";
import ReactDOM from "react-dom";
import Home from "./components/Home.js";

ReactDOM.hydrate(<Home />, document.querySelector("#root")); 
  • hydrate: 수분을 공급하다.
  • react event를 작동시키기 위한 react 코드를 전달해야함. hydrate라는 메서드가 그 역할을 한다.

6) index.js 수정

import express from "express";
const app = express();
import React from "react";
import { renderToString } from "react-dom/server";
import Home from "./components/Home.js";

app.use(express.static("public")); // express.static(): 지정해둔 디렉토리에 있는 정적파일을 클라이언트로 제공해라라는 메서드

app.get("/", (req, res) => {
  const html = `
    <html>
        <head></head>
        <body>
            <div id="root"></div>
            <script src="bundle.js"></script>
        </body>
    </html>
  `;
  res.send(html);
}); 

app.listen(3000, () => {
  console.log("3000번 포트가 열렸습니다!");
});


느낀점

  • CSR과 SSR의 동작 원리를 정확히 알게 되어 좋았다.
  • React를 SSR로 구동시켜 본 것이 신기했다.
  • 많이 사용해보지 않았던 Webpack 설정을 해보아서 좋았고 Webpack공부를 조금 더 해봐야겠다는 생각이 들었다.

이 글은 멋쟁이사자처럼 한재현 강사님 수업시간에 배운 내용 + 제가 추가적으로 공부한 내용을 바탕으로 하고 있습니다.

profile
🙌꿈꾸는 프론트엔드 개발자 신은수입니당🙌

0개의 댓글