배꼽파지 않도록 잘 개발해요

[엘리스sw] 13주차 1일 - SSR, 배포 본문

교육/엘리스 SW 학습 내용

[엘리스sw] 13주차 1일 - SSR, 배포

꼽파 2024. 3. 26. 06:48


Server Side Rendering

◆  Server Side Rendering

◆  성능 측정 키 메트릭

◆  Server Side Rendering 이해

◆  React를 활용한 Server Side Rendering

◆  SSR 구현하기 (실습)

 

React 앱 빌드와 배포

◆   React 앱 배포

◆   배포를 위한 React 앱 준비

◆   Azure를 사용한 VM 배포

◆   React 앱 배포를 위한 Azure VM 세팅

◆   앱 배포하기 (실습)


Server Side Rendering 

Server Rendering

 

Server Side Rendering (SSR)

  • React, Vue, Angular 등 자바스크립트 프레임워크가 나오기 이전 초기 웹 환경에서는 모든 페이지를 서버에서 빌드.
  • 클라이언트는 별도의 처리없이 웹페이지 노출.

Client Side Rendering (CSR)

  • Ajax 등의 기술(XML HTTP Request), 자바스크립트 프레임워크를 활용하여, 데이터를 받아 자바스크립트로 페이지를 동적으로 만들 수 있게 됨.
  • 데이터는 XML, JSON 형태로 클라이언트에 전송.

CSR의 장점

  • CSR는 자바스크립트만으로 완전히 페이지를 만들 수 있음.
  • 자바스크립트를 최대한으로 활용하여 HTML, CSS를 동적으로 생성.
  • 컴포넌트 단위로 코드를 나누고, 다양한 디자인 패턴을 적용하는 등, 클라이언트 개발의 수준을 한 단계 끌어올림.
  • Full page load 없이 라우팅.

CSR의 단점

  • 자바스크립트 코드가 많으면 앱 로딩이 느려짐.
  • SEO가 좋지 않음.

출처 : 엘리스SW트랙 강의

 

검색엔진은 전체 HTML, CSS, JS를 받아오는 경우가 드물고 '크롤러(crawler)'라는 기계가 서버에 페이지를 요청함.
서버는 HTML, CSS, JS를 내려줄 수 있지만 크롤러는 HTML만을 읽어서 페이지에 어떤 정보가 있는지 판단하게 됨.
크롤러가 HTML을 읽어서 얻은 정보를 어떤 DB에 저장하게 됨.
유저가 Search 엔진에서 검색을 했을 때 DB에서 정보를 꺼내게 됨.
그럴 때 HTML을 내려주는데 CSR의 경우는 클라이언트 브라우저에서 페이지를 만듦.
초기 HTML을 내려줄 때는 정보가 별로 없음.
→ 크롤러는 이런 JS를 돌리지 않아서 HTML에 어떤 정보도 없게 됨.

 

Server Side Rendering

  • 서버에서 자바스크립트를 이용해 페이지를 미리 빌드.
  • 컴포넌트 생성에 필요한 API 요청, routing, redux store 생성 등을 처리.
  • 클라이언트는 빌드된 페이지와 자바스크립트를 받아, 웹앱을 CSR처럼 동작하게 함(hydration).
  • 이런 특징으로, Universal Rendering이라고도 함.

출처 : 엘리스SW트랙 강의

 


성능 측정 키 메트릭

웹 퍼포먼스

  • 웹 페이지가 로드되고 유저와 상호작용하는 모든 것들을 측정.
  • 성능을 측정하여 웹앱의 사용성을 개선할 수 있음.
  • 열악한 네트워크 환경에서도 사용 가능한 앱을 만드는 등 좋은 유저 경험으로 유저의 만족을 얻음.

 

Time To First Byte (TTFB)

  • 페이지 요청 후, 처음 데이터가 도착하기까지 걸리는 시간.
  • 요청을 받았을 때, 서버에서 처리하는 시간이 오래 걸리나, 네트워크가 딜레이되는 등의 상황 발생 시 지표가 악화됨.

 

First Contentful Paint

  • 페이지에 진입하고부터, 브라우저가 어떤 DOM Content를 만들 때까지 걸리는 시간.
  • 페이지 진입 후 FCP까지 평균 3초 이상 걸리면 성능 개선이 필요함.

 

Time to Interactive

  • 웹페이지 진입 후, 유저가 클릭, 스크릭, 인풋 등의 행위를 하기까지 걸리는 시간.
  • 자바스크립트가 로드되고 나서, 이벤트 핸들러 등이 부착되어 입력을 처리할 수 있기까지의 시간.


Server Side Rendering 이해

CSR의 페이지 로드 방식

 

SSR의 페이지 로드 방식

 

SSR의 페이지 로드 방식

  • 유저가 빠르게 페이지의 내용을 볼 수 있도록 HTML을 미리 빌드하여 FCP 등의 키 메트릭을 개선함.
  • 서버 자원을 활용하여, 초기 큰 성능이 필요한 페이지 등을 빌드하는 데 활용.


SSR의 장점

  • Crawler는 페이지를 indexing하기 위해 페이지에 관한 많은 정보가 필요.
  • SSR을 활용하여 미리 페이지를 빌드하면, Crawler에게 많은 정보를 줄 수 있음.
  • SEO(Search Engine Optimization)에 유리.


SSR의 단점

  • CSR에 비해 TTFB에 불리함.
  • 별도의 서버를 유지하는 비용.
  • Static rendering보다 CDN Caching에 불리.

 

  • TTFB(Time to First Byte)에 불리함: SSR은 서버에서 HTML을 동적으로 생성하고 클라이언트에게 전송하기 때문에 초기 로딩 속도가 느릴 수 있음. 클라이언트가 서버로부터 받아오는 첫 번째 바이트까지 걸리는 시간이 길어질 수 있음.
  • 서버 유지 비용: SSR을 사용하려면 서버 측에서 페이지를 렌더링할 수 있는 서버 인프라가 필요함. 이를 위해 별도의 서버를 유지하는 데 추가적인 비용이 발생할 수 있음.
  • CDN 캐싱에 불리함: SSR은 동적으로 페이지를 생성하기 때문에 캐싱이 CSR에 비해 더 어려울 수 있음. 정적 콘텐츠를 CDN에 캐싱하는 것보다 SSR에서 생성된 동적 콘텐츠를 캐싱하는 것이 더 복잡하고 비효율적일 수 있습니다. 따라서 CDN 캐싱의 이점을 최대로 활용하기 어려울 수 있음.

 

  • CDN (Content Delivery Network) : 지리적인 제약 없이 전 세계 사용자에게 빠르고 안전하게 컨텐츠 전송을 할 수 있는 기술
  • 캐싱(Caching) : CDN이 각 위치에 캐시 서버(에지, Edge)를 두고 인접한 지역에서 가장 많이 요청되는 콘텐츠를 저장해 놓는 것.

 

▼ 캐시 관련 내용 참고
https://www.cloudflare.com/ko-kr/learning/cdn/what-is-caching/


React를 활용한 Server Side Rendering

ReactDOMServer

  • ReactDOMServer를 활용하여, 특정 React Component를 HTML로 빌드.
  • Node.js 서버에서 JSX를 사용하여 페이지 빌드.

renderToString

  • React Component를 HTML로 변환함.
  • 클라이언트의 페이지 요청 시 변환된 HTML string을 전달.
  • renderToNodeStream은 readable stream을 생성.
  • 브라우저가 받아서 점진적으로 페이지를 그림.

ReactDOM.hydrate

  • renderToString으로 생성한 HTML의 root을 기준으로, 받아온 React code를 통해 markup에 이벤트 핸들러를 등록하는 등 컴포넌트화.

hydration 시 주의할 점

  • 서버에서 생성한 컴포넌트와 브라우저에서 Hydration을 거친 후의 마크업이 다르면, React runtime은 경고를 보냄.
    ex) 현재 시간을 보여주는 컴포넌트
  • 경고 발생 시, 어느 부분에서 차이점이 생기는지 반드시 파악해야 함.
  • componentDidMount 역할을 하는 useEffect의 경우, SSR 시 서버에서 동작하지 않음.
  • data loading 등의 처리를 별도로 해주어야 할 필요가 있음.

SSR 구현하기

터미널에서 명령어 입력

 

npm 패키지.json이 설치됨.

npm init -y

 

react와 react-dom을 설치함.

npm install react react-dom

 

webpack과 Babel을 설치

npm i -D webpack webpack-cli
npm i -D @babel/core @babel/preset-env @babel/preset-react babel-loader

 

Babel과 webpack을 연결하는 Babel loader를 설치

npm i -D babel-loader

 

npm script에 'Webpack' 명령어 추가

build : Webpack을 올리는 스크립트

 

index.js

scr/client 폴더 안에 index.js 작성
react-dom을 찾아서 Element를 렌더링함.

import React from 'react'
import ReactDOM from 'react-dom'

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
 	<div>
		<Test>
 	</div>
);


import React from 'react'

function App() {

	const [ count, setCount ] = useState(0);

	const decreaseClick = () => setCount(c => c - 1);
	const increaseClick = () => setCount(c => c + 1);

	return (
		<div>
			<div>
				Count : {count}
			</div>

			<div>
				<button onClick={decreaseClick}>Decrease</button>
				<button onClick={increaseClick}>Increase</button>
			</div>
		</div>
	)
}

 

webpack.config.js

const path = require('path')

module.exports = {
	mode: 'development',
	target: 'web',
	entry: './src/client/index.js',
	output: {
		filename: 'bundle.js',
		path: path.resolve(__dirname, 'public')
	},

	module: {
		rules: [
		{
			test: /.jsx?$/,
			loader: 'babel-loader',
			exclude: /node_modules/,
			options: {
				presets: [
					"@babel/preset-env", "@babel/preset-react"
				],
			},
		},],
	},
};

 

npm run build

 

서버 관련 라이브러리 설치

- express 설치

- webpack-node-externals 설치

(webpack에서 node-modules를 설치하지 않고 서버에서 공유하기 위한 라이브러리)

npm i express webpack-node-externals

 

서버 코드 작성 시작

index.js에서 서버코드를 작성할 수 있게 됨.

 

index.js

const express = require("express");

const app = express();

app.use(express.static("public"));

app.get("*", (req, res) => {
	res.send("Hi");
});

app.listen(4000, () => {
	console.log('4000번 포트에서 서버 구동...');
})

 

script에 "server"로 "node src/index.js" 실행하도록 추가

 

서버 실행

npm run server

 

localhost 4000 접속시 'Hi'가 브라우저 화면에 뜨는 것을 알 수 있음.

 

Server Side Rendering을 위해서 서버를 세팅

const express = require("express");
const ReactDOMServer = require("react-dom/server");
const React = require("react");
const App = require("./client/App").default;

const app = express();

app.use(express.static("public"));

app.get("*", (req, res) => {
	const html = ReactDOMServer.renderToString(<App />);
	res.send(html);
})

app.listen(4000, () => {
	console.log("4000번 포트에서 서버 구동...");
})

 

JSX코드가 서버에 있는 것이 좀 의심스러움.

아니나 다를까 에러가 팡팡 터진다.
jsx는 node.js에서 알 수 없기 때문에 서버의 코드를 또다시 빌드를 해줘야 된다.

 

webpack.server.js

서버의 코드를 빌드하기 위한 코드임.

 

package.json

build 폴더의 bundle.js로 코드가 나옴.

 

server의 src의 js가 아니라 build의 bundle.js를 돌려야 함.

script의 명령어를 수정하기

 

클릭하면 이 앱이 동작하지 않는 것을 알 수 있음.
app.js가 만든 html을 리턴했지만 정작 동작하기 위한 리액트 코드는 전송하지 못했기 때무임.
리액트코드까지 같이 전송해야 함.

 

bundle.js에는 최신 리액트 앱이 들어있어야 함.

클라이언트 앱 빌드

npm run build


서버 빌드

npm run server

 

src 폴더에 있는 index.js 파일로 가기

여기서 App을 사용하고 있지 않으니, App을 import한다.

 

 

npm run build과 npm run server로 다시 실행해준다.

 

일반적으로 SSR(Server-Side Rendering)을 사용할 경우 초기 페이지 로드 시에 서버에서 렌더링된 HTML이 클라이언트에 의해 받아지기 때문에, 페이지의 초기 상태는 서버에서 렌더링된 컨텐츠를 포함하게 됨. 따라서 SSR을 사용하는 경우에는 클라이언트 측에서의 초기 렌더링에 대한 대기 없이 페이지의 초기 상태를 확인할 수 있음.

 

반면에 CSR(Client-Side Rendering)을 사용하는 경우에는 초기 HTML 로드 시에 페이지의 초기 상태는 비어있을 가능성이 높음. 이후 JavaScript가 실행되고 React 앱이 초기화되면, 페이지는 동적으로 렌더링되어 id="root"와 같은 요소에 React 앱이 렌더링됨.

 

따라서 네트워크 탭에서 페이지의 초기 상태를 확인할 때, SSR을 사용한 페이지의 경우에는 초기 상태가 클라이언트 측에서 렌더링된 컨텐츠를 포함하고 있을 가능성이 높음. 반면에 CSR을 사용한 페이지의 경우에는 초기 상태가 비어 있을 가능성이 높음.


React 앱 배포

React 앱 배포 Overview

  • 인터넷에서 내가 만든 앱에 접근할 수 있어야 함.
  • 지속적으로 앱을 수정하고 배포해야 함.
  • Public IP 주소로 직접 접근할 수 있도록 함.
  • IP를 부여받은 서버에 React 앱을 배포.
  • 앱을 서빙하는 웹서버를 통해 사용자에게 앱을 전달.
  • 사용자는 IP를 통해 앱에 접근.

 

React 앱 배포 프로세스

  • IP를 부여받은 서버(VM)에 React 앱을 배포.
  • 앱을 빌드하고, 웹서버를 세팅.
  • 앱을 서빙하는 웹서버를 통해 사용자에게 앱을 전달.
  • 사용자는 필요한 데이터를 받아 앱을 로딩.

프론트엔드 앱 배포 시 유의할 점

  • 서버와 통신 시, CORS가 허용되었는지 점검.
  • 브라우저, 디바이스별로 앱이 정상적으로 동작하는지 점검.
  • 앱의 로딩 속도, 각 동작 시 성능, 버그 등을 점검.

배포를 위한 React 앱 준비

React 앱 준비

  • yarn.lock, package-lock.json이 동시에 존재하지 않는지 검검.
  • 로컬에서 npm run build를 실행하여, 빌드 시 에러가 발생하지 않는지 점검.
  • 로컬에서 배포하여, production build가 제대로 실행되는지 점검.

Gitlab 연동

git remote add origin https://gitlab.com/{gitlab_id}/{project_name}
git push --set-upstream origin master

 

  • 작성한 프로젝트 코드를 Gitlab에 배포
  • 프로젝트가 Gitlab에 잘 올라갔는지 점검
  • last commit까지 적용되었는지 점검

Azure를 사용한 VM 배포

  • Azure를 사용한 VM 배포
  • portal.azure.com에 접속.
  • Virtual machine에 접속.
  • Create > Virtual machine 버튼을 틀릭

 

VM 설정

  • SSH public key로 설정.
  • key pair는 다운받음.
  • 유저 이름은 azureuser로 유지.

  • 포트 접근은 모두 허용함.

Review & Create 버튼을 눌러, 마지막으로 점검한 뒤에 생성.

 

접근 테스트

  • 다운받은 private key를 .ssh 밑으로 옮김.
  • ssh 커맨드로 VM 서버에 접근.
my {private_key} ~/.ssh
ssh -i ./{private_key} azureuser@{ip_address}
  • 다운받은 private key를 .ssh 밑으로 옮긴다.
  • ssh 커맨드로 VM 서버에 접근한다.


React 앱 배포를 위한 Azure VM

React 앱 배포를 위한 Azure VM

  • VM에서 node.js, npm을 설치.
  • 앱을 빌드하는데 필요한 npm package를 설치.
  • serve를 이용해 앱을 배포.

Node.js NPM 설치

sudo apt update
sudo apt install nodejs
sudo apt install npm

 

프로젝트 패키지 설치

  • 프로젝트 코드를 git clone으로 다운.
  • npm i로, 빌드에 필요한 패키지를 설치.
git clone https://gitlab.com/{gitlab_id}/{project_name}
cd {project_name}
npm i

 

빌드 후 배포

  • 프로젝트를 빌드.
  • serve 웹서버를 사용해 프로젝트를 80번 포트에서 서빙.
sudo npm i -g serve
npm run build
sudo -s -p 80 build

 

앱 접근 테스트

  • 브라우저로 IP 주소에 접근해 앱이 서빙되는지 테스트.


앱 배포하기

앱 빌드하기

npm run build

 

이 파일은 유저 브라우저로 서빙할 총 번들의 크기임.

 

빌드가 잘 된 것을 확인할 수 있음.

 

해당 명령어를 통해 로컬에서 서빙해보도록 할 것임.

 

서브 설치

npm i -g serve

 

정적 빌드 파일을 로컬 서버를 통해서 제공

serve -s build

 

결과물이 잘 나오는 것을 확인할 수 있음.

 

다음 과정은 깃랩 배포라서 필요할 경우 영상 참고.

필자는 AW3를 활용할 예정임.

 

private key를 저장해서 컴퓨터에 옮겨야 함.

 

 

로컬 컴퓨터 안에 있는 private key 확인

cat (VM이름)

 

VM 배포가 완료됨.

 

728x90