클라이언트 사이드 렌더링(CSR)
- UI 렌더링을 브라우저에서 모두 처리하는 것
- 서버에 html 문서를 요청하는 것이 아니라, 브라우저에서 자바 스크립트로 콘텐츠를 렌더링 하는 것
- body 안에 id = "root"만 들어있고, 어플리케이션에 필요한 자바스크립트의 링크만 들어가 있다
- html이 비어있기 때문에 처음 접속하면 빈 화면만 보이게 된다
[단점]
- 사용자가 첫 화면을 보기까지의 시간이 오래 걸릴 수 있다는 점
- 썩 좋지 않은 SEO(Search Engine Optimization)
- * SEO : 구글과 네이버와 같은 검색 엔진들은 서버에 등록된 웹 사이트를 돌아다니면서 웹 사이트의 HTML 문서를 분석해서 우리가 검색할 때 웹 사이트를 빠르게 검색할 수 있도록 도와준다
- 하지만 CSR에서 사용되어지는 HTML의 바디는 앞 선 예제처럼 대부분 텅텅 비어있기 때문에 검색엔진들이 CSR로 작성된 웹 페이지를 분석하는데 어려움을 겪고 있음
서버 사이드 렌더링 (SSR)
- 서버로부터 요청해서 받은 내용을 브라우저 화면에 표시해주는 것
- 서버 사이드 렌더링을 구현하면 사용자가 웹 서비스에 방문했을 때 서버 쪽에서 초기 렌더링을 대신 해준다
- 그리고 사용자가 html을 전달받을 때 그 내부에 렌더링된 결과물이 보인다
CSR은 페이지의 내용을 브라우저에서, 그리고 SSR은 서버에서 페이지의 내용을 다 그려서 브라우저로 던져준다.
[장점]
- 구글, 네이버, 다음 등의 검색 엔진이 우리가 만든 웹 애플리케이션의 페이지를 원활하게 수집할 수 있음
- 웹 서비스의 검색 엔진 최적화를 위해서라면 서버 사이드 렌더링을 구현해주는 것이 좋음
- 서버 사이드 렌더링을 통해 초기 렌더링 성능을 개선할 수 있음
- 서버 사이드 렌더링을 구현한 웹 페이지라면 자바스크립트 파일 다운로드가 완료되지 않은 시점에서도 html 상에 사용자가 볼 수 있는 콘텐츠가 있기 때문에 대기 시간이 최소화된다
[단점]
- 원래 브라우저가 해야 할 일을 서버가 대신 처리하는 것이므로 서버 리소스가 사용된다는 단점이 있음
- 갑자기 수많은 사용자가 동시에 웹 페이지에 접속하면 서버에 과부하가 발생할 수 있음
- 따라서 사용자가 많은 서비스라면 캐싱과 로드 밸런싱을 통해 성능을 최적화해 주어야 한다
- Blinking Issue
- 사용자가 새로고침을 하게 되면 전체 웹 사이트를 다시 서버에서 받아와야 하기 때문에 화면이 없어졌다가 나타남
- UX 관점에서 봤을 때 좋지 않음
- TTV(Time To View)와 TVI(Time To Interact)의 공백 시간
- SSR 방식에서는 서버에서 만들어진 HTML 파일을 가져오게 되고 사용자는 바로 웹 사이트를 볼 수 있다
- 하지만 웹 사이트를 동적으로 제어할 수 있는 자바스크립트 파일은 아직 받아오지 않았기 때문에 사용자가 클릭을 해도 아무런 것도 처리할 수 없는 상태가 된다
- 최정족으로 자바스크립트 파일을 받아와야지만 사용자가 원하는 것을 처리할 수 있는 인터랙션이 가능해진다
- → 그래서 SSR은 사용자가 사이트를 볼 수 있는 시간(TTV)와 실제로 인터랙션이 가능한 시간(TTI)의 공백 시간이 꽤 길다
- 반대로 CSR은 HTML만 받아왔을 때는 아무것도 보여지지 않고 링크되어 있는 모든 로직을 처리하는 자바스크립트 파일을 받아오게 되면 웹 사이트가 보여지는 동시에 인터랙션이 가능해진다. TTV와 TTI의 공백시간이 없는것.
- 서버 사이드 렌더링을 하면 프로젝트의 구조가 좀 더 복잡해질 수 있고, 데이터 미리 불러오기, 코드 스플리팅과의 호환 등 고려해야할 사항이 더 많아져서 개발이 어려워진다
서버 사이드 렌더링과 코드 스플리팅 충돌
- 서버 사이드 렌더링과 코드 스플리팅을 함께 적용하면 페이지에 깜박임이 발생함
- 이러한 이슈를 해결하려면 라우트 경로마다 코드 스플리팅된 파일 중에서 필요한 모든 파일을 브라우저에서 렌더링하기 전에 미리 불러와야 한다
→ Loadable Components 라이브러리에서 제공하는 기능을 써서 서버 사이드 렌더링 후 필요한 파일의 경로를 추출하여 렌더링 결과에 스크립트/스타일 태그를 삽입해 주는 방법으로 이 문제를 해결한다
[서버 사이드 렌더링 구현] ··코드 생략··
서버 사이드 렌더링을 구현하려면 웹팩 설정을 커스터마이징 해주어야 한다
1 서버 사이드 렌더링용 엔트리 만들기
엔트리(entry)는 웹팩에서 프로젝트를 불러올 때 가장 먼저 불러오는 파일
- 예를 들어 리액트 프로젝트에서는 index.js를 엔트리 파일로 사용함
- 서버 사이드 렌더링을 할 때는 서버를 위한 엔트리 파일을 따로 생성해야 한다
2 서버 사이드 렌더링 전용 웹팩 환경 설정
- config/path.js 파일
- ssrIndexJs는 불러올 파일의 경로이고, ssrBuild는 웹팩으로 처리한 뒤 결과물을 저장할 경로
ssrIndexJs: resolveApp('src/index.server.js'), // 서버 사이드 렌더링 엔트리
ssrBuild:resolveApp('dist'), // 웹팩 처리 후 저장 경로
- 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, // 정적 파일이 제공될 경로
},
};
- 서버 사이드 렌더링을 할 때 CSS 혹은 이미지 파일은 그다지 중요하지 않지만 완전히 무시할 수도 없음
- 그래서 해당 파일을 로더에서 별도로 설정하여 처리하지만 따로 결과물에 포함되지 않도록 구현함
3 빌드 스크립트 작성하기
4 서버 코드 작성하기
5 정적 파일 제공하기
- Express에 내장되어 있는 static 미들웨어를 사용해 서버를 통해 build에 있는 JS, CSS 정적 파일들에 접근하도록 함
데이터 로딩 ··코드 생략··
데이터 로딩을 한다는 것은 API 요청을 의미한다
- 서버의 경우 문자열 형태로 렌더링하는 것이므로 state나 리덕스 스토어의 상태가 바뀐다고 해서 자동으로 리렌더링되지 않음
1. redux-thunk 코드 준비
2. PreloadContext 만들기
- 렌더링 하기 전에 API를 요청한 뒤 스토어에 데이터를 담아야 한다.
- 서버 환경에서 이러한 작업을 하려면 constructor 메서드를 사용하거나 render 함수 자체에서 처리해야 한다. 그리고 요청이 끝날 때 까지 대기했다가 다시 렌더링해주어야 한다.
- 이 작업을 PreloadContext로 처리함
- lib/PreloadContext.js
import { createContext, useContext } from "react";
// 클라이언트 환경: null
// 서버 환경: { done: false, promise: []}
const PreloadContext = createContext(null);
export default PreloadContext;
// resolve는 함수 타입입니다.
export const Preloader = ({ resolve }) => {
const preloadContext = useContext(PreloadContext);
if (!preloadContext) return null; // context 값이 유효하지 않다면 아무것도 하지 않음
if (preloadContext.done) return null; // 이미 작업이 끝났다면 아무것도 하지 않음
// promises 배열에 프로미스 등록
// 설령 resolve 함수가 프로미스를 반환하지 않더라도, 프로미스 취급을 하기 위해
// Promise.resolve 함수 사용
preloadContext.promises.push(Promise.resolve(resolve()));
return null;
};
3. 서버에서 리덕스 설정 및 PreloadContext 사용하기
- 서버가 실행될 때 스토어를 한번만 만드는 것이 아니라, 요청이 들어올 때마다 새로운 스토어를 만든다
- renderToString 대신 renderToStaticMarkup 함수를 사용한 이유는 그저 Preloader로 넣어 주었던 함수를 호출하기 위해서다.
- 또 이 함수의 처리 속도가 renderToString보다 좀 더 빠르기 때문
4. 스크립트로 스토어 초기 상태 주입하기
- 서버에서 만들어 준 상태를 브라우저에서 재사용하려면, 현재 스토어 상태를 문자열로 변환한 뒤 스크립트로 주입해주어야 한다
1. redux-saga 코드 준비하기
2. redux-saga를 위한 서버 사이드 렌더링 작업
- redux-thunk를 사용하면 preloader를 통해 호출한 함수들이 Promise를 반환하지만, redux-sage를 사용하면 Promise를 반환하지 않기 때문에 추가 작업이 필요하다
- modules/index.js, src/index.js에 사가(sage) 추가
3. usePreloader Hook 만들어 사용
import { createContext, useContext } from "react";
// 클라이언트 환경: null
// 서버 환경: { done: false, promise: []}
const PreloadContext = createContext(null);
export default PreloadContext;
// resolve는 함수 타입입니다.
export const Preloader = ({ resolve }) => {
const preloadContext = useContext(PreloadContext);
if (!preloadContext) return null; // context 값이 유효하지 않다면 아무것도 하지 않음
if (preloadContext.done) return null; // 이미 작업이 끝났다면 아무것도 하지 않음
// promises 배열에 프로미스 등록
// 설령 resolve 함수가 프로미스를 반환하지 않더라도, 프로미스 취급을 하기 위해
// Promise.resolve 함수 사용
preloadContext.promises.push(Promise.resolve(resolve()));
return null;
};
// Hook 형태로 사용할 수 있는 함수
export const usePreloader = (resolve) => {
const preloadContext = useContext(Preloader);
if (!preloadContext) return null;
if (preloadContext.done) return null;
preloadContext.promises.push(Promise.resolve(resolve()));
};
요즘에는 CSR이나 SSR 말고 SSG(Static Site Generation)라는 것도 있음
Gatsby
React는 CSR에 최적화되어 있는 라이브러리지만 Gatsby라는 라이브러리와 함께 사용하면 리액트로 만든 웹 애플리케이션을 정적으로 웹 페이지를 미를 생성해두고 서버에 배포할 수 있다.
Next.JS
개츠비 다음으로 리액트에서 많이 사용되는 것이 Next.js이다.
Next.js는 강력한 서버 사이드 렌더링을 지원하는 라이브러리였는데, 요즘에는 SSG도 지원하고 CSR과 SSR을 섞어 더 강력하고 유연하게 목적에 맞게 사용할 수 있도록 지원해준다.
SSR 개념 이해와 Next.js로 실습까지 해보는 SSR 환경 구축하기
서론 안녕하세요, 영훈입니다. 엄청 오랜만에 글을 썼네요. 이번 글에서는 SSR은 어떤 개념이고 react와 같은 SPA에서 어떻게 SSR 환경을 쉽게 구축하는지에 알아보려고 합니다. 또한 실습을 통해서
velog.io
'React' 카테고리의 다른 글
[React] 리덕스 (1) | 2023.06.01 |
---|---|
[React] 백엔드 프로그래밍 : Node.js의 Koa 프레임워크 (0) | 2023.05.03 |
[React] 코드 스플리팅 (0) | 2023.05.01 |
[React] 리덕스 미들웨어를 통한 비동기 작업 관리 (0) | 2023.04.14 |
[React] 리덕스를 사용해 상태 관리 (0) | 2023.04.06 |