Deploy React App

LOOPY·2022년 8월 29일
1
post-thumbnail

1. SPA 프로젝트 배포 이해하기

👉🏻 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


2. serve 패키지로 React Web App 배포하기

1) > npm install serve -g: serve라는 패키지를 전역으로 설치
2) > serve -s build: serve 명령어를 -s 옵션으로 build 폴더를 지정하여 실행

  • 이 때 -s 옵션(SPA의 약자)이 어떤 라우팅으로 요청해도 index.html을 응답하도록 하는 역할을 수행한다.

3. AWS S3에 React Web App 배포하기

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은 적용되지 않은 경우이므로 따로 적용이 필요하다!)


4. node.js express로 React Web App 배포하기

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);
  • nodejs로 실행되는 파일, 내장 api는 require()으로 받아와 사용
  • express.static()으로 static 서버로 제공할 파일 경로 지정
    • path.join()으로 경로 생성 -> build를 가리키도록
    • nodejs의 글로벌 변수인 __dirname은 현재 파일 실행되는 dirname
  • npm run build의 결과물이 static 서버로 제공됨 -> 따라서 경로가 존재하지 않는 경우 오류 발생(cannot get ...)
const 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을 내려줄 수 있다.


6. 서버사이드 렌더링 이해하기

Server Side Rendering(SSR)

  • 서버에서 응답을 가져올 때, 기존처럼 static file만을 가져오는 것이 아니고, 먼저 서버에서 응답 값을 만들어서 내려준 후 static file을 내려주는 방식
  • static file을 다 내려받고 리액트 앱을 브라우저에서 실행한 뒤에는 SPA처럼 동작
  • React Component를 브라우저가 아니라 Node.js에서 사용
  • ReactDOMServer.renderToString(<App />);
    • 결과가 문자열이고, 이를 응답으로 내려줌
  • 라우팅, 리덕스와 같은 처리를 서버에서 진행하고 내려줘야 하기 때문에 복잡하고 어려움
  • JSX가 포함된 리액트 코드를 서버에서 읽을 수 있도록 babel 설정을 해야 함

// 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);
profile
1.5년차 프론트엔드 개발자의 소소한 기록을 담습니다 :-)

0개의 댓글