👉🏻 SPA(Single Page Application)
cmd 창에서 > nvm use
, > npm ci
이후
(create-react-app 프로젝트에서) > npm run build
: production 모드로 빌드되어 'build' 폴더에 파일이 생성되고, 이 파일들을 웹 서버를 통해 사용자가 접근할 수 있도록 처리하는 배포 과정을 거친다.
이 때는 'build/static' 폴더 안에 JS, CSS 파일들이 생성되고 파일 이름에 hash 값이 붙으며, long term caching techniques 라는 기술이 적용되어 있고, cdn 사용 시 유용하다.
여기서 static서버는 파일 하나하나를 파일 서버처럼 웹 서버로 제공하는 서버를 이야기하는데, 서버에서 db를 사용하거나 추가적 작업을 한다면 해당 static 서버로는 한계가 있다. 단지 local에서 index.html을 더블 클릭해서 열듯이 서버에 그런 파일들을 가져다 두는 의미로 이해할 수 있고, 이런 경우에는 파일을 (원격에서 요청하면) 내려주는 방식의 서버만 있으면 react app을 사용할 수 있다.
따라서 server에 html을 요청하면 -> 링크된 파일(css, js 등)이 모두 유저의 client(browser)에게 내려가 -> react app을 사용할 수 있게 되는 구조이다.
문제는, 위와 같이 보통의 일반적인 static 서버는 어떤 파일을 요청하면 그 파일을 주는데, react app은 index.html 파일을 제외하고 나머지 라우팅되는 경로에 해당하는 여러 html 파일이 존재하지 않는다. 그래서 client에서 라우팅하는 SPA 방식은 만약 파일이 없으면 index.html 내려주는 방식을 사용해야 한다.
SPA Deploy 특징
(모든 요청을 서버에 하고 받아오는 형태가 아니고) 라우팅 경로에 상관없이 리액트 앱을 받아 실행하며, 라우팅은 받아온 리액트 앱을 실행 후 적용하고, static 파일을 제외한 모든 요청을 index.html로 응답해주도록 작업해야한다.
이렇게 index.html을 내려주는 방식은 크게 네 가지가 있다.
1) serve -s build
2) AWS S3에 배포
3) node.js express
4) NginX
1) > npm install serve -g
: serve라는 패키지를 전역으로 설치
2) > serve -s build
: serve 명령어를 -s 옵션으로 build 폴더를 지정하여 실행
1) 고유한 이름으로 버킷 생성
2) 해당 버킷으로 이동해 파일 업로드 (build 폴더 내 모든 파일 + static 폴더)
3) 버킷을 정적 웹사이트로 변경
/
경로 시 페이지)에 index.html 등록 -> 오류 문서(없는 경로 시 페이지)에도 index.html 등록 -> 저장4) 객체를 public으로 설정
// Public 정책
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3::bucket-name/*"]
}
]
}
5) 하단 주소로 진입
(이런 경우, https나 ssl은 적용되지 않은 경우이므로 따로 적용이 필요하다!)
1) 프로젝트 폴더에서 > npm i express
2) > code .
로 vs code 진입
3) 루트 경로에 server.js 파일 생성
// server.js
const express = require('express');
const path = require('path');
const app = express();
app.use(express.static(path.join(__dirname, 'build')));
app.listen(9000);
require()
으로 받아와 사용express.static()
으로 static 서버로 제공할 파일 경로 지정path.join()
으로 경로 생성 -> build를 가리키도록__dirname
은 현재 파일 실행되는 dirnameconst express = require('express');
const path = require('path');
const app = express();
// 경로가 있는 경우
app.use(express.static(path.join(__dirname, 'build')));
// 경로가 없는 경우(cannnot get) index.html 내려줌
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.listen(9000);
이렇게 작성하면, 경로가 없는 모든 경우(*)에 index.html을 내려줄 수 있다.
ReactDOMServer.renderToString(<App />);
// server.js
const express = require('express');
const path = require('path');
// SSR로 rendering 하기 위해 CSR에서 구현하고 있는 Component를 String으로 만듬
const ReactDOMServer = require('react-dom/server');
// Component 및 react Element를 만들기 위해 React를 가져옴
const React = require('react');
// html 조작 위해 File System API 가져옴
const fs = require('fs');
const app = express();
app.use(express.static(path.join(__dirname, 'build')));
// SSR
// '/test' 경로로 접속시 가공한 html 파일을 제공
app.get('/test', (req, res) => {
// SSR로 먼저 표현할 element 생성하여 가공 쉽게 string으로 만들기
// JSX 코드는 직접 사용 불가 -> 결과물은 <div>Hello</div>
const ssr = ReactDOMServer.renderToString(React.createElement('div', null, 'Hello'));
// SSR 구현시킬 해당 page html(index.html) 가져와 string으로 만들기
// index.html에서 바꿀 부분을 replace로 채워 넣음
const indexHtml = fs.readFileSync(path.join(__dirname, 'build', 'index.html')).toString().replace('<div id="root"></div>, '<div id="root">${ssr}</div>');
// SSR 작업이 완료된 index.html을 보냄
res.send(indexHtml);
});
// CSR
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'build', 'index.html'));
});
app.listen(9000);