1. Create React App (CRA) ← Only CSR
- "Create React apps with no build configuration."
- 아무런 초기 설정 없이도 CRA를 통해 React 기반의 SPA 사이트를 구현할 수 있게 됨.
- (과장 조금 보태서) 터미널에 npx create-react-app my-app, npm run build 명령어 만으로 사이트 하나를 만드는 것이 가능.
- webpack, babel 등 복잡한 세팅을 거치지 않아 React 기반 웹 프로젝트 확산에 큰 기여.
- 그런데 얼마 간 시간이 지나면서 조금씩 이상한 점이 드러나기 시작.
- "우리 사이트가 검색에 너무 안 걸리는걸?"
- 원인) CRA로 build한 프로젝트는 Only CSR(Client Side Render)로 실행되기 때문.
- CSR (Client Side Rendering)
- 웹 페이지의 렌더링이 클라이언트(브라우저) 측에서 일어나는 것을 의미.
- 브라우저는 최초 요청에서 html, js, css 확장자의 파일을 차례로 다운로드.
- 최초로 불러온 html의 내용은 비어있음. (html, body 태그만 존재)
- js 파일의 다운로드가 완료된 다음, 해당 js 파일이 dom을 빈 html 위에 그리기 시작.
- 백엔드 호출을 최소화 할 수 있음
- 최초 호출 때만 html, js, css를 요청
- 이후에는 화면에서 변화가 일어나야 하는 만큼의 데이터만 요청 (ex. JSON)
- 라우팅(새로운 페이지로 이동)을 하더라도 html 자체가 바뀌는 것이 아니라 JavaScript 차원에서 새로운 화면을 그려내는 것!
2. SEO (Search Engine Optimization)
- 하지만 웹 크롤러 입장에서 CSR로 구성된 페이지에 접속하는 시나리오를 재구성해보자.
- 웹 크롤러가 각 사이트를 돌아다니며 조사를 하는 상황이라고 가정.
CSR
- 똑똑똑~
- (끼익) ← 문이 열린다 (페이지 요청)
- (빈 집) ← 첫 페이지
- (조사 못하고 구글봇 퇴장)
- (js 파일 다운로드 되며 페이지 로드) ← 뒷북
- ⇒ 검색 노출 안 됨
SSR
- 똑똑똑~
- (끼익) ← 문이 열린다 (페이지 요청)
- (주인이 웹 크롤러를 맞이하며) "구글봇님 어서오세요. 저희 사이트는..." ← 첫 페이지
- (조사를 마친 구글봇 퇴장)
- ⇒ 검색 노출 됨
- CRA로 신나게 만들었던 페이지들이 검색 노출이 잘 안된다는 (충격적인) 사실이 알려지면서, SEO에 민감한 커머스 등의 비즈니스 영역은 대안을 찾아야 하는 상황.
- "SPA의 장점을 살리면서 SEO도 같이 챙길 수는 없을까?"
- ⇒ SSR (Server Side Rendering)
3. SSR (Server Side Rendering)
- 위에서 언급했던 CSR과 SSR의 차이에서 알 수 있듯, SSR은 서버에서 첫 페이지의 렌더링을 클라이언트 측이 아닌 서버 측에서 처리해주는 방식.
- CSR과 비교하면,
- 1) UX 측면에서 유리
- CSR에 비해 페이지를 구성하는 속도는 늦어질 수 있지만, 전체적으로 사용자에게 보여주는 콘텐츠 구성이 완료되는 시점은 빨라진다.
- Code Splitting?
- 만약 첫 페이지 구성에 불필요한 JS 파일을 받아온다면?
- 그 번들 파일의 크기가 크다면?
- 불필요한 내용들은 받아오지 않고 서버 단에서 첫 페이지에 필요한 정적인 부분만 처리한 뒤 JS는 나중에 필요한 부분만 필요할 때 로드할 수 있다면?
- 2) SEO 측면에서 유리
- 서버에서 사용자에게 보여줄 페이지를 모두 구성하여 사용자에게 보여주는 방식이기 때문에 CSR의 단점인 "첫 페이지 깡통" 상태를 극복할 수 있음.
- 주의) 페이지를 잘못 구성할 경우 CSR에 비해 서버 부하가 커지거나 / 첫 로딩이 매우 느려질 수 있음
- 1) UX 측면에서 유리
4. SPA for SSR
SSR for MPA, SSR for SPA...?
- 이쯤 되면 고개가 또 다시 갸우뚱 해진다. 🤔
- "2세대 웹의 JSP, PHP, Django Template 같은 것들 역시 SSR이 아니었나요?" ⇒ 맞습니다! ✅
- "그러면 CSR의 한계를 극복하기 위해 웹이 2세대 기술로 돌아가고 말았나요?" ⇒ 아니에요! 🚫
- "SPA랑 CSR이랑 같은 의미 아니었나요?" ⇒ 아니에요! 🚫
- 우리가 지금 얘기해볼 SSR은 SSR for SPA
- SPA(결과물) / CSR, SSR (렌더링 방법)로 나누어 이해하면 좋음.
5. CSR + SSR?
- 그렇다면 SSR과 CSR을 섞어 쓸 수는 없나요
- 사용할 수 있다!
- 1) 첫 렌더 SSR
- 2) 그 이후 렌더 부터는 CSR
:: 원리 & 구조
- SSR은 다음과 같은 요소로 구성된다
- node.js로 구성된 FE 서버
- pyhton, django로 되어 있는 BE 서버
- 다음과 같은 과정을 거쳐 SSR이 진행된다 (링크)
- 유저가 브라우저에 **/**를 입력.
- 미리 실행되고 있는 FE 서버가 요청을 받고 서버사이드 렌더링.
- 만들어진 html 을 브라우저에게 보냄.
- 브라우저가 응답받은 html 을 그림.
- html 에 기능을 부여할 **index.js**파일을 다운로드 받음. (hydration)
- 다운로드가 완료된 이후, go to second 링크를 클릭.
- **/second**로 라우팅하고 second 페이지 코드를 생성.
:: 예제 코드
import React from "react";
import ReactDom from "react-dom";
import App from "./App";
const initialData = window.__INITIAL_DATA__;
ReactDom.hydrate(
<App page={initialData?.page} />,
document.getElementById("root")
);
// 출처: <실전 리액트 프로그래밍>, 이재승 저
index.js
hydration
- 서버에서 전송한 정적 문서를 데이터 변경에 반응할 수 있는 동적 형태로 변환하는 클라이언트 측 프로세스를 말한다.
- render와 동일하지만, dom은 이미 그려져 있는 상태이기 때문에 event listener만 부착하는 식으로 작동.
<!DOCTYPE html>
<html>
<head>
<title>ssr test</title>
<script type="text/javascript">
window.__INITIAL_DATA__ = __DATA_FROM_SERVER__;
</script>
__STYLE_FROM_SERVER__
</head>
<body>
<div id="root"></div>
</body>
</html>
index.html
import express from "express";
import fs from "fs";
import path from "path";
import * as url from "url";
import { renderToString } from "react-dom/server";
import React from "react";
import { ServerStyleSheet } from "styled-components";
import App from "./App";
const app = express();
const html = fs.readFileSync(
path.resolve(__dirname, "../dist/index.html"),
"utf8"
);
app.use("/dist", express.static("dist"));
app.get("favicon.ico", (req, res) => res.sendStatus(204));
app.get("*", (req, res) => {
const parsedUrl = url.parse(req.url, true);
const page =
parsedUrl.pathname && parsedUrl.pathname !== "/"
? parsedUrl.pathname.substr(1)
: "home";
const sheet = new ServerStyleSheet();
const renderString = renderToString(sheet.collectStyles(<App page={page} />));
const styles = sheet.getStyleTags();
const initialData = { page };
const result = html
.replace('<div id="root"></div>', `<div id="root">${renderString}</div>`)
.replace("__DATA_FROM_SERVER__", JSON.stringify(initialData)) // head script에 server 측 주입
.replace("__STYLE_FROM_SERVER__", styles); // head script에 데이터 주입
res.send(result);
});
app.listen(3000);
server.js
6. Next.js
- 생산성을 위해 Next.js가 주로 채택됨
Next.js by Vercel - The React Framework
Production grade React applications that scale. The world’s leading companies use Next.js by Vercel to build static and dynamic websites and web applications.
nextjs.org
◦ SSR의 CRA, 간단히 구성 가능
◦원티드, 토스, 배민, 카카오커머스 등 사용 중
import React from "react";
import Link from "next/link"; // next/link 사용하여 라우팅 -> CSR 동작
export default function About({ data }) {
return (
<div>
<Link href="/">about</Link> {data}
</div>
);
}
About.getInitialProps = function () {
return { data: "data" };
};
-
- 첫 페이지를 "/"로 호출했을 때 페이지 자체가 SSR 후 HTML 파일로 완성되어 돌아옴
- 1번 이후 Link 컴포넌트를 사용하여 라우팅을 하면 CSR이 이뤄지고 서버로부터 about.js만 새로 받아온 뒤 화면을 그리게 됨
- (전부 CSR로 할 땐 js를 한 번에 다운로드 받습니다. 그래서 about.js를 추가적으로 다운받지 않습니다. 서버에서 추가적으로 about.js를 다운받는건 SSR이후 CSR을 하기 위해서입니다.)
- 2번에서 CSR로 이동했던 "/about" 페이지를 첫 호출로 불러오게 되면 아까는 CSR로 불러와졌던 페이지가 SSR로 호출되었음을 확인할 수 있음 (about.html)
장점
- SSR 외에도 React App에 필요한 여러가지 필수 기능들을 제공하는 Framework 역할
- 페이지 기반 라우팅 시스템 (동적 라우팅 지원)
- pre-rendering , 페이지별 정적파일 생성과 서버사이드 렌더링 지원
- 코드 스플리팅
- CSS, Sass 기본 지원 및 다른 CSS-in-JS 라이브러리 지원
- 등등...
추가자료 (SSR CSR 상세동작 순서)
[CS / Q] SSR CSR의 상세동작 순서, node.js (window객체)
1. SSR, CSR의 상세동작 순서 더보기 1-1. SSR의 동작 순서 Server Side Rendering의 약자. 서버쪽에서 렌더링 준비를 끝마친 상태로 클라이언트에 전달하는 방식이다. 1. User가 Website 요청을 보냄. 2. Server..
higher77.tistory.com
Reference
- 어서 와, SSR은 처음이지? - 도입 편 (Naver D2)
- 웹 렌더링 (Google Developers)
- ReactDOM.hydrate() (React Docs)
- 브라우저는 어떻게 동작하는가? (Naver D2)
https://workatit.tistory.com/m/74
'CS > 자료' 카테고리의 다른 글
[CS] 쿠키, 세션스토리지, 로컬스토리지 (0) | 2021.12.15 |
---|---|
정적파일, 동적파일, 미디어파일 (0) | 2021.11.11 |
node.js, 웹서버, WAS, 웹컨테이너, 리버스프록시 (0) | 2021.11.11 |
인증 & 인가 (0) | 2021.06.29 |
콘솔, 터미널, 쉘 차이점 (0) | 2021.06.20 |