나는 오늘도 멋있다

React(TS) - SSR(WebPack) 기본설정 - 1 본문

Web/WebPack

React(TS) - SSR(WebPack) 기본설정 - 1

나는 오늘도 멋있다 2023. 11. 21. 19:33

 

[학습계기]

NextJS를 학습하려다가 너무 돌아갔다가 지금 도착했다.. Webpack을 공부하고, module의 차이점을 비교하였다. 단순히 NextJs만 사용해서 SSR을 단순히 구현할 수 있지만 NextJS가 React의 프레임워크 인데 React로는 SSR을 구현할수 없을까? 라는 의문점과 NextJS 의 편의성을 좀더 직관적으로 느껴보고 싶어서 React로 SSR을 구현해보고 싶었다. 이것저것 고생도 했지만 해보길 잘한 것 같다.

 

 

 

[클라이언트 설정]

 

클라이언트 구조

1. react & typescript 패키지 설치

npm i react react-dom react-router-dom
npm i -D typescript @types/react @types/react-dom

 

2. 기본파일구성

/* src/client/index.html */

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>React SSR Test</title>
</head>
<body>
    <div id="root"></div>
</body>
</html>
/* src/index.tsx*/

import React from "react";
import { hydrateRoot } from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App";

hydrateRoot(
  document.getElementById("root") as HTMLElement,
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);
/* src/App.tsx */

import Navbar from "./components/Navbar";
import Counter from "./components/Counter";
import About from "./components/About";
import { Routes, Route } from "react-router-dom";

function App(){

    return(
       <main>
        <Navbar/>
        <Routes>
            <Route path="/" element={<Counter/>}/>
            <Route path="/about" element={<About/>}/>
        </Routes>
       </main>
    );
}

export default App;

 

3.  webpack 패키지

npm i -D fork-ts-checker-webpack-plugin html-webpack-plugin
npm i -D ts-loader
npm i -D webpack webpack-cli webpack-dev-server

 

4. 클라이언트 WebPack 설정

/* root/WebPack.client/js */

import path from "path";
import HtmlWebpackPlugin from "html-webpack-plugin";
import ForkTsCheckerWebpackPlugin from "fork-ts-checker-webpack-plugin";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url))


export default {
    target: "web",
    mode: "development",
    entry: "./src/client/index.tsx",
    output: {
        path: path.join(__dirname, "/dist"),
        filename: "client.js",
    },
    devServer:{
        open: false,
        hot: true,
        port: 3000,
        historyApiFallback: true,
        liveReload: true
    },
    module: {
        rules:[
              {
                test: /\.(ts|tsx)$/,
                use: "ts-loader",
                exclude: /node_modules/,

              }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({
            template: `${__dirname}/src/client/index.html`
        }),
        new ForkTsCheckerWebpackPlugin(),

    ],
    resolve:{
        extensions: [".js",".ts",".tsx"],
    }

}

 

 

[서버설정]

 

서버 구조

1. express 패키지 설치

npm i express
npm i -D @types/express

 

2. 기본파일구성

/* src/server/index.tsx */

import express, {Request, Response} from 'express';
import ReactDOMServer from "react-dom/server";
import { StaticRouter } from "react-router-dom/server";
import App from "../client/App";
import fs from "fs";

const app = express();
const port = 3001;
const reactHtml = async (url: string)=>{
    const reactApp = ReactDOMServer.renderToString(
        <StaticRouter location={url}>
            <App/>
        </StaticRouter>
    )
    const html = await fs.promises.readFile(`${__dirname}/index.html`, "utf-8");
    const reactHtml = html.replace(
        '<div id="root"></div>',`<div id="root">${reactApp}</div>`
    )


    return reactHtml;
}


app.get("*", async(req: Request, res: Response)=>{
    
    const indexHtml =  await reactHtml(req.url)
    res.send(indexHtml)
})


app.listen(port, ()=>{
    console.log(`server Start ${port}`)
})

 

3. 서버 WebPack 설정

/* root/WebPack.server.js */

import path from "path";
import { fileURLToPath } from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url))


export default {
    target: "node",
    mode: "development",
    entry: "./src/server/index.tsx",
    output: {
        path: path.join(__dirname, "/dist"),
        filename: "server.cjs",
    },
    module: {
        rules:[
              {
                test: /\.(ts|tsx)$/,
                use: "ts-loader",
                exclude: /node_modules/,

              }
        ]
    },
    resolve:{
        extensions: [".js",".ts",".tsx"],
    }
}

 

4.Package.json Script 설정

"scripts": {
    "dev": "webpack-dev-server --config webpack.client.js",
    "clean": "rm -rf ./dist",
    "build": "npm run clean && webpack --config webpack.client.js && webpack --config webpack.server.js",
    "start": "node ./dist/server.cjs"
  },

 

 

 

해당 구조를 가지고 build 한 파일을 실행하면 SSR로 구현이 되는것을 확인하였다. 네트워크 탭에서 html에 내용을 가지고 응답을 한다.

하지만 문제점이 많다. 내가 구현하고 싶은것은 기본적인것이 아니다.

1. SSR로 초기렌더링을 하고 CSR로 페이지의 부분동작

2. build 하지않고 개발서버에서 확인

 

위의 두가지가 풀리지않고있다. 거의 이틀정도가 걸린것 같다... CommonJS 와 ESM 때문에도 고생했다.

그래도 시간낭비만 하는 삽질은 아니였다. package.json 과 tsconfig.json을 엄청 만지작 거렸다. 시간은 걸릴지 몰라도 내가 원하는것을

구현하기까지는 계속 해볼생각이다.  (Next.js로 포토폴리오를 만들고난후에 ... )

 

https://github.com/JangIkIk/React/tree/main/SSR%20with%20WebPack (git)

해당글은 시간에 쫒겨서 설명이 부족한 관계로 git을 참고하는게 좋을것 같다.

ps. 위에 적은 package만으로도 기본으로 동작한다.  다른package는 내가테스트하는 이유로 남겨두었다.