CSR 방식은 서버가 최소한의 HTML 문서를 제공하고, 실제 콘텐츠 렌더링은 클라이언트에서 JavaScript로 수행하는 방식입니다. 주로 SPA(Single Page Application) 형태의 웹사이트에 널리 사용됩니다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>My React App</title>
<script src="/static/js/bundle.js"></script> <!-- 번들된 JS -->
</head>
<body>
<div id="root"></div> <!-- React가 렌더링될 컨테이너 -->
<noscript>이 사이트는 JavaScript를 필요로 합니다.</noscript>
</body>
</html>
import { createRoot } from 'react-dom/client';
import App from './App';
createRoot(document.getElementById('root')).render(<App />);
SSR은 요청을 받을 때마다 서버가 HTML 콘텐츠를 동적으로 생성하여 클라이언트에 전달하는 방식으로, 초기 콘텐츠 표시가 매우 빠르고 SEO에 유리합니다.
import express from 'express';
import { renderToString } from 'react-dom/server';
import App from './App';
const app = express();
app.get('*', (req, res) => {
const html = renderToString(<App />);
res.send(`
<!DOCTYPE html>
<html>
<body>
<div id="root">${html}</div>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
app.listen(3000);
SSG 방식은 빌드 과정에서 미리 모든 콘텐츠를 정적 HTML 파일로 생성하여 제공하는 방법입니다. 문서, 블로그, 마케팅 페이지 등 정적 콘텐츠 위주로 구성된 사이트에서 주로 사용됩니다.
// pages/posts/[id].jsx (Next js)
export default function Post({ post }) {
return <div>{post.title}</div>;
}
export async function getStaticPaths() {
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map(post => ({ params: { id: post.id } }));
return { paths, fallback: false };
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.id}`);
const post = await res.json();
return { props: { post } };
}
Server-Side Rendering(SSR)은 초기 로딩 속도를 높이고 SEO(Search Engine Optimization)에 유리한 환경을 제공합니다. 그러나 서버에서 생성된 HTML은 기본적으로 정적이어서, 사용자와의 동적인 상호작용을 처리할 수 없습니다. 이를 보완하기 위해 등장한 것이 바로 Hydration입니다.
Hydration은 서버가 렌더링하여 클라이언트로 전달한 정적 HTML을 클라이언트 측 JavaScript와 연결하여 동적으로 만드는 과정입니다. 이 과정이 없으면 사용자는 화면을 보기는 하지만 버튼 클릭같은 기본적인 인터렉션을 수행할 수 없습니다.
React의 Hydration 과정은 크게 세 단계로 이루어집니다.
서버에서 받은 HTML과 클라이언트에서 생성한 가상 DOM(vDOM)을 비교하여, 서로 일치하는 부분은 재사용하고 일치하지 않는 부분은 재렌더링합니다.
서버에서 내려온 HTML 요소에 React가 관리하는 이벤트 핸들러와 상태를 연결하여 상호작용 가능한 상태로 만듭니다.
Hydration이 완료되면, 사용자는 정적인 페이지가 아닌 완전히 동작 가능한 인터랙티브한 애플리케이션을 이용할 수 있게 됩니다.
페이지 전체가 준비될 때까지 기다리지 않고 HTML 콘텐츠를 조각(chunk) 단위로 나누어 스트리밍 형태로 클라이언트에 전달할 수 있습니다. 이를 통해 사용자는 더 빨리 콘텐츠를 확인할 수 있고, 일부 콘텐츠가 늦게 도착하더라도 사용자 경험에 큰 영향을 미치지 않습니다.
모든 콘텐츠를 한 번에 Hydration하지 않고 중요한 요소부터 우선적으로 Hydration합니다. 이를 통해 사용자가 실제로 보는 콘텐츠부터 빠르게 활성화되고, 나머지 요소는 필요에 따라 점진적으로 활성화됩니다.
일부 콘텐츠의 Hydration을 사용자의 특정 행동(예: 스크롤, 클릭)까지 연기하는 전략입니다. 초기 로딩 속도를 높이고 전체 애플리케이션 성능을 효과적으로 향상시킬 수 있습니다.
페이지 전체를 Hydration할 필요 없이, 인터랙티브한 부분만 Hydration하고 정적인 부분은 그대로 둠으로써 성능 부담을 최소화합니다.
페이지를 한 번에 Hydration하지 않고 중요도에 따라 순차적으로 처리합니다. 주요 콘텐츠는 즉시 Hydration하고, 덜 중요한 콘텐츠는 사용자의 브라우저가 유휴 상태일 때 처리하여 성능 효율성을 극대화합니다.
페이지를 독립적인 UI 모듈(섬)로 나누어 각 섬이 독자적으로 Hydration을 수행하도록 합니다. 이를 통해 각 컴포넌트가 서로 독립적으로 동작하며, 전체 페이지 성능이 개선됩니다.
참고
Server-side Rendering (SSR)
SPA와 SSG, 그리고 SSR
웹에서 렌더링
Next.js의 Hydrate란?
React의 hydration mismatch 알아보기
Understanding React Hydration
react hydration test case