일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 추상메서드
- 컬렉션 타입
- 다형성
- EnhancedFor
- cursor문
- 객체 비교
- 대덕인재개발원
- 메소드오버로딩
- 오라클
- 환경설정
- 인터페이스
- NestedFor
- exception
- 정수형타입
- 집합_SET
- 컬렉션프레임워크
- 예외처리
- GRANT VIEW
- 한국건설관리시스템
- Java
- 어윈 사용법
- abstract
- 생성자오버로드
- 사용자예외클래스생성
- 제네릭
- 예외미루기
- oracle
- 참조형변수
- 자바
- 자동차수리시스템
- Today
- Total
거니의 velog
(2) 서버 사이드 렌더링 2 본문
3. 서버 사이드 렌더링 구현하기
* 서버 사이드 렌더링을 구현하려면 웹팩 설정을 커스터마이징해 주어야 한다. CRA로 만든 프로젝트에서는 웹팩 관련 설정이 기본적으로 모두 숨겨져 있으니 yarn eject 명령어를 실행하여 밖으로 꺼내 주자.
$ git add .
$ git commit -m'Commit before eject'
$ yarn eject
(1) 서버 사이드 렌더링용 엔트리 만들기
* 엔트리(entry)는 웹팩에서 프로젝트를 불러올 때 가장 먼저 불러오는 파일이다. 예를 들어 현재 작성 중인 리액트 프로젝트에서는 index.js를 엔트리 파일로 사용한다. 이 파일부터 시작하여 내부에 필요한 다른 컴포넌트와 모듈을 불러오고 있다.
* 서버 사이드 렌더링을 할 때는 서버를 위한 엔트리 파일을 따로 생성해야 한다. src 디렉터리에 index.server.js 라는 파일을 생성하자.
import React from "react";
import ReactDOMServer from "react-dom/server";
const html = ReactDOMServer.renderToString(
<div>Hello Server Side Rendering</div>
);
console.log(html);
* 지금은 가장 기본적인 코드만 작성했다. 서버에서 리액트 컴포넌트를 렌더링할 때는 ReactDOMServer의 renderToString이라는 함수를 사용한다. 이 함수에 JSX를 넣어서 호출하면 렌더링 결과를 문자열로 반환한다.
(2) 서버 사이드 렌더링 전용 웹팩 환경 설정 작성하기
* 작성한 엔트리 파일을 웹팩으로 불러와서 빌드하려면 서버 전용 환경 설정을 만들어 주어야 한다. 먼저 config 경로의 path.js 파일을 열어서 스크롤을 맨 아래로 내린 후 module.exports 부분에 다음과 같이 두 줄을 추가해 주자.
// config after eject: we're in ./config/
module.exports = {
dotenv: resolveApp(".env"),
appPath: resolveApp("."),
appBuild: resolveApp(buildPath),
appPublic: resolveApp("public"),
appHtml: resolveApp("public/index.html"),
appIndexJs: resolveModule(resolveApp, "src/index"),
appPackageJson: resolveApp("package.json"),
appSrc: resolveApp("src"),
appTsConfig: resolveApp("tsconfig.json"),
appJsConfig: resolveApp("jsconfig.json"),
yarnLockFile: resolveApp("yarn.lock"),
testsSetup: resolveModule(resolveApp, "src/setupTests"),
proxySetup: resolveApp("src/setupProxy.js"),
appNodeModules: resolveApp("node_modules"),
appWebpackCache: resolveApp("node_modules/.cache"),
appTsBuildInfoFile: resolveApp("node_modules/.cache/tsconfig.tsbuildinfo"),
swSrc: resolveModule(resolveApp, "src/service-worker"),
ssrIndexJs: resolveApp("src/index.server.js"), // 서버 사이드 렌더링 엔트리
ssrBuild: resolveApp("dist"), // 웹팩 처리 후 저장 경로
publicUrlOrPath,
};
module.exports.moduleFileExtensions = moduleFileExtensions;
* 이 파일에는 ssrIndexJs와 ssrBuild 정보를 추가했다. ssrIndexJs는 불러올 파일의 경로이고, ssrBuild는 웹팩으로 처리한 뒤 결과물을 저장할 경로이다.
* 다음으로 웹팩 환경 설정 파일을 작성한다. config 디렉터리에 webpack.config.server.js 파일을 생성해 주자.
const paths = require("./paths");
module.exports = {
mode: "production", // 프로덕션 모드로 설정하여 최적화 옵션들을 활성화
entry: paths.ssrIndexJs, // 엔트리 경로
target: "node", // node 환경에서 실행될 것이라는 점을 명시
output: {
path: paths.ssrBuild, // 빌드 경로
filename: "server.js", // 파일 이름
chunkFilename: "js/[name].chunk.js", // 청크 파일 이름
publicPath: paths.publicUrlOrPath, // 정적 파일이 제공될 경로
},
};
* 웹팩 기본 설정을 작성했다. 빌드할 때 어떤 파일에서 시작해 파일들을 불러오는지, 또 어디에 결과물을 저장할지를 정해 주었다.
* 다음으로 로더를 설정한다. 웹팩의 로더는 파일을 불러올 때 확장자에 맞게 필요한 처리를 해 준다. 예를 들어 자바스크립트는 babel을 사용하여 트랜스파일링을 해주고, CSS는 모든 CSS 코드를 결합해 주고, 이미지 파일은 파일을 다른 경로에 따로 저장하고 그 파일에 대한 경로를 자바스크립트에서 참조할 수 있게 해 준다.
* 서버 사이드 렌더링을 할 때 CSS 혹은 이미지 파일은 그다지 중요하지 않다. 그렇다고 완전히 무시할 수는 없다. 가끔 자바스크립트 내부에서 파일에 대한 경로가 필요하거나 CSS Module처럼 로컬 classname을 참조해야 할 수도 있기 때문이다. 그래서 해당 파일을 로더에서 별도로 설정하여 처리하지만 따로 결과물에 포함되지 않도록 구현할 수 있다.
[config/webpack.config.server.js]
const paths = require("./paths");
const getCSSModuleLocalIdent = require("react-dev-utils/getCSSModuleLocalIdent"); // CSS Module의 고유 className을 만들 때 필요한 옵션
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
module.exports = {
mode: "production", // 프로덕션 모드로 설정하여 최적화 옵션들을 활성화
entry: paths.ssrIndexJs, // 엔트리 경로
target: "node", // node 환경에서 실행될 것이라는 점을 명시
output: {
path: paths.ssrBuild, // 빌드 경로
filename: "server.js", // 파일 이름
chunkFilename: "js/[name].chunk.js", // 청크 파일 이름
publicPath: paths.publicUrlOrPath, // 정적 파일이 제공될 경로
},
module: {
rules: [
{
oneOf: [
// 자바스크립트를 위한 처리
// 기존 webpack.config.js 를 참고하여 작성
{
test: /\.(js|mjs|jsx|ts|tsx)$/,
include: paths.appSrc,
loader: require.resolve('babel-loader'),
options: {
customize: require.resolve(
'babel-preset-react-app/webpack-overrides'
),
plugins: [
[
require.resolve('babel-plugin-named-asset-import'),
{
loaderMap: {
svg: {
ReactComponent: '@svgr/webpack?-svgo![path]'
}
}
}
]
],
cacheDirectory: true,
cacheCompression: false,
compact: false
}
},
// CSS 를 위한 처리
{
test: cssRegex,
exclude: cssModuleRegex,
// exportOnlyLocals: true 옵션을 설정해야 실제 css 파일을 생성하지 않습니다.
loader: require.resolve('css-loader'),
options: {
exportOnlyLocals: true
}
},
// CSS Module 을 위한 처리
{
test: cssModuleRegex,
loader: require.resolve('css-loader'),
options: {
modules: true,
exportOnlyLocals: true,
getLocalIdent: getCSSModuleLocalIdent
}
},
// Sass 를 위한 처리
{
test: sassRegex,
exclude: sassModuleRegex,
use: [
{
loader: require.resolve('css-loader'),
options: {
exportOnlyLocals: true
}
},
require.resolve('sass-loader')
]
},
// Sass + CSS Module 을 위한 처리
{
test: sassRegex,
exclude: sassModuleRegex,
use: [
{
loader: require.resolve('css-loader'),
options: {
modules: true,
exportOnlyLocals: true,
getLocalIdent: getCSSModuleLocalIdent
}
},
require.resolve('sass-loader')
]
},
// url-loader 를 위한 설정
{
test: [/\.bmp$/, /\.gif$/, /\.jpe?g$/, /\.png$/],
loader: require.resolve('url-loader'),
options: {
emitFile: false, // 파일을 따로 저장하지 않는 옵션
limit: 10000, // 원래는 9.76KB가 넘어가면 파일로 저장하는데
// emitFile 값이 false 일땐 경로만 준비하고 파일은 저장하지 않습니다.
name: 'static/media/[name].[hash:8].[ext]'
}
},
// 위에서 설정된 확장자를 제외한 파일들은
// file-loader 를 사용합니다.
{
loader: require.resolve('file-loader'),
exclude: [/\.(js|mjs|jsx|ts|tsx)$/, /\.html$/, /\.json$/],
options: {
emitFile: false, // 파일을 따로 저장하지 않는 옵션
name: 'static/media/[name].[hash:8].[ext]'
}
}
]
}
]
}
};
* 이제 코드에서 node_module 내부의 라이브러리를 불러올 수 있게 설정한다.
[config/webpack.config.server.js]
const paths = require("./paths");
const getCSSModuleLocalIdent = require("react-dev-utils/getCSSModuleLocalIdent"); // CSS Module의 고유 className을 만들 때 필요한 옵션
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
module.exports = {
mode: "production", // 프로덕션 모드로 설정하여 최적화 옵션들을 활성화
entry: paths.ssrIndexJs, // 엔트리 경로
target: "node", // node 환경에서 실행될 것이라는 점을 명시
output: {
path: paths.ssrBuild, // 빌드 경로
filename: "server.js", // 파일 이름
chunkFilename: "js/[name].chunk.js", // 청크 파일 이름
publicPath: paths.publicUrlOrPath, // 정적 파일이 제공될 경로
},
module: {
(...)
},
resolve: {
modules: ["node-modules"],
},
};
* 이렇게 했을 때 react, react-dom/server 같은 라이브러리를 import 구문으로 불러오면 node_module에서 찾아 사용한다. 라이브러리를 불러 오면 빌드할 때 결과물 파일 안에 해당 라이브러리 관련 코드가 함께 번들링된다.
* 브라우저에서 사용할 때는 결과물 파일에 리액트 라이브러리와 우리의 애플리케이션에 관한 코드가 공존해야 하는데, 서버에서는 굳이 결과물 파일 안에 리액트 라이브러리가 들어 있지 않아도 된다. node_modules를 통해 바로 불러와서 사용할 수 있기 때문이다.
* 따라서 서버를 위해 번들링할 때는 node_modules에서 불러오는 것을 제외하고 번들링하는 것이 좋다. 이를 위해 webpack-node-externals라는 라이브러리를 사용해야 한다. 이 라이브러리를 yarn 명령어를 사용하여 설치해 주자.
$ yarn add webpack-node-externals
* 다음으로 이 라이브러리를 webpack.config.server.js의 상단에 불러와서 설정에 적용한다.
[config/webpack.config.server.js]
const nodeExternals = require("webpack-node-externals");
const paths = require("./paths");
const getCSSModuleLocalIdent = require("react-dev-utils/getCSSModuleLocalIdent"); // CSS Module의 고유 className을 만들 때 필요한 옵션
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
module.exports = {
mode: "production", // 프로덕션 모드로 설정하여 최적화 옵션들을 활성화
entry: paths.ssrIndexJs, // 엔트리 경로
target: "node", // node 환경에서 실행될 것이라는 점을 명시
output: {
path: paths.ssrBuild, // 빌드 경로
filename: "server.js", // 파일 이름
chunkFilename: "js/[name].chunk.js", // 청크 파일 이름
publicPath: paths.publicUrlOrPath, // 정적 파일이 제공될 경로
},
module: {
(...)
resolve: {
modules: ["node-modules"],
},
externals: [nodeExternals()],
};
* 이제 환경 설정 파일은 거의 작성했다. 마지막으로 환경 변수를 주입하자.
[config/webpack.config.server.js]
const nodeExternals = require("webpack-node-externals");
const paths = require("./paths");
const getCSSModuleLocalIdent = require("react-dev-utils/getCSSModuleLocalIdent"); // CSS Module의 고유 className을 만들 때 필요한 옵션
const webpack = require("webpack");
const getClientEnvironment = require("./env");
const cssRegex = /\.css$/;
const cssModuleRegex = /\.module\.css$/;
const sassRegex = /\.(scss|sass)$/;
const sassModuleRegex = /\.module\.(scss|sass)$/;
const env = getClientEnvironment(paths.publicUrlOrPath.slice(0, -1));
module.exports = {
mode: "production", // 프로덕션 모드로 설정하여 최적화 옵션들을 활성화
entry: paths.ssrIndexJs, // 엔트리 경로
target: "node", // node 환경에서 실행될 것이라는 점을 명시
output: {
path: paths.ssrBuild, // 빌드 경로
filename: "server.js", // 파일 이름
chunkFilename: "js/[name].chunk.js", // 청크 파일 이름
publicPath: paths.publicUrlOrPath, // 정적 파일이 제공될 경로
},
module: {
rules: [
(...)
],
},
resolve: {
modules: ["node-modules"],
},
externals: [nodeExternals()],
plugins: [
new webpack.DefinePlugin(env.stringified), // 환경 변수를 주입해 준다.
],
};
* 환경 변수를 주입하면, 프로젝트 내에서 process.env.NODE_ENV 값을 참조하여 현재 개발 환경인지 아닌지를 알 수 있다.
(3) 빌드 스크립트 작성하기
* 이번에는 방금 만든 환경 설정을 사용하여 웹팩으로 프로젝트를 빌드하는 스크립트를 작성해 보자. scripts 경로를 열어 보면 build.js라는 파일이 있다. 이 스크립트는 클라이언트에서 사용할 빌드 파일을 만드는 작업을 한다. 이 스크립트와 비슷한 형식으로 서버에서 사용할 빌드 파일을 만드는 build.server.js 스크립트를 작성해 보겠다.
[scripts/build.server.js]
process.env.BABEL_ENV = "production";
process.env.NODE_ENV = "production";
process.on("unhandleRejection", (err) => {
throw err;
});
require("../config/env");
const fs = require("fs-extra");
const webpack = require("webpack");
const config = require("../config/webpack.config.server");
const paths = require("../config/paths");
function build() {
console.log("Creating server build");
fs.emptyDirSync(paths.ssrBuild);
let compiler = webpack(config);
return new Promise((resolve, reject) => {
compiler.run((err, stats) => {
if (err) {
console.log(err);
return;
}
console.log(stats.toString());
});
});
}
build();
* 코드를 다 작성한 뒤에는 다음 명령어를 실행하여 빌드가 잘 되는지 확인해 보자.
$ node scripts/build.server.js
* 성공적으로 잘 실행되었는가? 이어서 다음 명령어를 실행하여 작성한 결과물이 잘 작동하는지 확인해 보자.
$ node dist/server.js
* 테스트 삼아 만들었던 JSX가 문자열 형태로 잘 렌더링되었다.
* 매번 빌드하고 실행할 때마다 파일 경로를 입력하는 것이 번거로울 수 있으니 package.json에서 스크립트를 생성하여 더 편하게 명령어를 입력할 수 있도록 하자.
[ package.json - script 부분 ]
"scripts": {
"start": "node scripts/start.js",
"build": "node scripts/build.js",
"test": "node scripts/test.js",
"start:server": "node dist/server.js",
"build:server": "node scripts/build.server.js"
},
* 이렇게 스크립트를 만들면 다음 명령어로 서버를 빌드하고 시작할 수 있다.
$ yarn build:server
$ yarn start:server
* 스크립트를 다 만들고 나서 짧아진 명령어로 다시 한 번 실행해 보자. 잘 작동한다. 이제 본격적으로 서버 사이드 렌더링을 구현할 준비가 되었다!
'React > React_백엔드 프로그래밍' 카테고리의 다른 글
(6) 서버 사이드 렌더링 6 (0) | 2023.12.16 |
---|---|
(5) 서버 사이드 렌더링 5 (0) | 2023.12.16 |
(4) 서버 사이드 렌더링 4 (0) | 2023.12.16 |
(3) 서버 사이드 렌더링 3 (0) | 2023.12.15 |
(1) 서버 사이드 렌더링 1 (0) | 2023.12.14 |