🎭 CSR vs SSR vs SSG
📖 정의
웹 페이지를 생성하고 보여주는 방식은 크게 세 가지로 나뉩니다:
- CSR (Client-Side Rendering): 브라우저에서 JavaScript로 페이지를 생성
- SSR (Server-Side Rendering): 서버에서 HTML을 완성해서 보내기
- SSG (Static Site Generation): 빌드 시점에 미리 HTML 생성
각 방식은 장단점이 있으며, 프로젝트 특성에 맞게 선택해야 합니다.
🎯 비유로 이해하기
레스토랑 비유
세 가지 렌더링 방식을 레스토랑에 비유하면:
CSR (Client-Side Rendering)
= 셀프 요리 레스토랑 🍳
고객(브라우저):
1. 레스토랑 입장 (빈 HTML 받기)
2. 재료 받기 (JavaScript 다운로드)
3. 직접 요리 (렌더링)
4. 완성된 음식 먹기
장점: 주방(서버) 부담 적음, 자유롭게 조리
단점: 요리 시간 필요, 초보자는 어려움
---
SSR (Server-Side Rendering)
= 일반 레스토랑 🍽️
주방(서버):
1. 주문 받기 (요청)
2. 요리하기 (HTML 생성)
3. 완성된 음식 서빙 (HTML 전송)
고객(브라우저):
즉시 먹을 수 있음!
장점: 빠른 초기 로딩, 검색엔진 친화적
단점: 주방 부담 큼, 매번 요리 필요
---
SSG (Static Site Generation)
= 도시락 체인점 🍱
빌드 타임:
1. 미리 모든 도시락 준비
2. 냉장고에 보관 (CDN)
고객(브라우저):
1. 주문
2. 즉시 포장된 도시락 받기
3. 먹기
장점: 초고속 배달, 저렴한 비용
단점: 메뉴 변경 어려움, 신선도 제한
건축 비유
CSR = 조립식 가구 (IKEA) 🪑
- 부품과 설명서 배송
- 집에서 직접 조립
- 배송비 저렴
- 조립 시간 필요
SSR = 맞춤 가구 🛋️
- 주문 제작
- 완성품 배송
- 바로 사용 가능
- 비용 높음, 시간 소요
SSG = 기성 가구 🪟
- 미리 만들어둠
- 창고에 보관
- 즉시 배송
- 저렴하고 빠름
- 맞춤 불가
⚙️ 작동 원리
1. CSR (Client-Side Rendering)
// CSR 흐 름
// 1단계: 서버에서 최소 HTML 전송
// index.html
<!DOCTYPE html>
<html>
<head>
<title>CSR App</title>
</head>
<body>
<div id="root"></div> <!-- 비어있음! -->
<script src="/bundle.js"></script>
</body>
</html>
// 2단계: 브라우저가 JavaScript 다운로드
// GET /bundle.js (2MB)
// 3단계: JavaScript 실행
ReactDOM.render(<App />, document.getElementById('root'));
// 4단계: API 호출하여 데이터 가져오기
fetch('/api/posts')
.then(res => res.json())
.then(data => setPost(data));
// 5단계: 화면 렌더링 완료
// 타임라인:
// 0ms: HTML 도착 (빈 화면)
// 100ms: JavaScript 다운로드 시작
// 1000ms: JavaScript 파싱 및 실행
// 1500ms: API 호출
// 2000ms: 데이터 도착
// 2100ms: 화면 표시 완료 ✅
// 사용자가 보는 것:
// 0-2100ms: 흰 화면 또는 로딩 스피너
// 2100ms~: 완성된 페이지
2. SSR (Server-Side Rendering)
// SSR 흐름
// 1단계: 클라이언트 요청
// GET /posts/123
// 2단계: 서버에서 데이터 가져오기
// server.js (Node.js + Express)
app.get('/posts/:id', async (req, res) => {
// 데이터베이스 조회
const post = await db.posts.findById(req.params.id);
// React 컴포넌트를 HTML 문자열로 변환
const html = ReactDOMServer.renderToString(
<PostPage post={post} />
);
// 완성된 HTML 전송
res.send(`
<!DOCTYPE html>
<html>
<head>
<title>${post.title}</title>
</head>
<body>
<div id="root">${html}</div>
<script>
window.__INITIAL_DATA__ = ${JSON.stringify(post)};
</script>
<script src="/bundle.js"></script>
</body>
</html>
`);
});
// 3단계: 브라우저가 HTML 받기
// 즉시 화면 표시 가능! (HTML이 완성되어 있음)
// 4단계: Hydration
// JavaScript가 로드되면 이벤트 리스너 연결
ReactDOM.hydrate(<PostPage />, document.getElementById('root'));
// 타임라인:
// 0ms: 요청
// 50ms: 서버에서 데이터 조회
// 100ms: HTML 생성
// 200ms: HTML 도착
// 250ms: 화면 표시 ✅ (빠름!)
// 1000ms: JavaScript 로드
// 1100ms: Hydration 완료 (인터랙티브)
// 사용자가 보는 것:
// 0-250ms: 로딩
// 250-1100ms: 볼 수 있지만 클릭 안 됨
// 1100ms~: 완전히 동작
3. SSG (Static Site Generation)
// SSG 흐름
// 빌드 타임 (배포 전)
// next build
// 1단계: 모든 페이지 미리 생성
// pages/posts/[id].js
export async function getStaticPaths() {
// 생성할 페이지 목록
const posts = await db.posts.findAll();
return {
paths: posts.map(post => ({
params: { id: post.id.toString() }
})),
fallback: false
};
}
export async function getStaticProps({ params }) {
// 각 페이지의 데이터
const post = await db.posts.findById(params.id);
return {
props: { post }
};
}
// 2단계: HTML 파일 생성
// .next/server/pages/posts/1.html
// .next/server/pages/posts/2.html
// .next/server/pages/posts/3.html
// ...
// 3단계: CDN에 배포
// Vercel, Netlify, CloudFront 등
// 런타임 (사용자 요청 시)
// GET /posts/123
// CDN이 즉시 HTML 반환 (초고속!)
// 타임라인:
// 0ms: 요청
// 10ms: CDN에서 HTML 반환 ✅ (엄청 빠름!)
// 500ms: JavaScript 로드
// 600ms: Hydration 완료
// 사용자가 보는 것:
// 0-10ms: 로딩
// 10-600ms: 볼 수 있지만 클릭 안 됨
// 600ms~: 완전히 동작
💡 실제 예시
CSR 예시 (Create React App)
// src/App.js
import React, { useState, useEffect } from 'react';
function App() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
// 브라우저에서 API 호출
fetch('https://api.example.com/posts')
.then(res => res.json())
.then(data => {
setPosts(data);
setLoading(false);
})
.catch(error => {
console.error('Error:', error);
setLoading(false);
});
}, []);
if (loading) {
return <div>로딩 중...</div>;
}
return (
<div className="app">
<h1>블로그 포스트</h1>
<ul>
{posts.map(post => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</li>
))}
</ul>
</div>
);
}
export default App;
// 장점:
// 1. 서버 부하 없음
// 2. 페이지 전환 빠름 (SPA)
// 3. 풍부한 인터랙션
// 단점:
// 1. 초기 로딩 느림
// 2. SEO 어려움 (빈 HTML)
// 3. JavaScript 필수
// 적합한 경우:
// - 관리자 대시보드
// - 로그인 후 사용하는 앱
// - SEO 불필요한 서비스
SSR 예시 (Next.js)
// pages/posts/[id].js
import { useRouter } from 'next/router';
function Post({ post }) {
const router = useRouter();
// 로딩 상태 (fallback)
if (router.isFallback) {
return <div>로딩 중...</div>;
}
return (
<article>
<h1>{post.title}</h1>
<p>{post.author}</p>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
// 서버에서 실행 (요청마다)
export async function getServerSideProps({ params }) {
// 데이터베이스 또는 API 호출
const res = await fetch(`https://api.example.com/posts/${params.id}`);
const post = await res.json();
// 404 처리
if (!post) {
return {
notFound: true
};
}
// props로 전달
return {
props: {
post
}
};
}
export default Post;
// 서버에서 생성된 HTML:
// <!DOCTYPE html>
// <html>
// <head>
// <title>Next.js</title>
// </head>
// <body>
// <div id="__next">
// <article>
// <h1>제목입니다</h1>
// <p>작성자: 홍길동</p>
// <div>
// <p>본문 내용...</p>
// </div>
// </article>
// </div>
// <script src="/_next/static/chunks/main.js"></script>
// </body>
// </html>
// 장점:
// 1. 빠른 초기 로딩
// 2. SEO 최적화
// 3. 실시간 데이터
// 단점:
// 1. 서버 비용 높음
// 2. 응답 시간 변동
// 3. 캐싱 어려움
// 적합한 경우:
// - 뉴스 사이트
// - 소셜 미디어 피드
// - 실시간 업데이트 필요
SSG 예시 (Next.js)
// pages/blog/[slug].js
function BlogPost({ post }) {
return (
<article>
<h1>{post.title}</h1>
<time>{post.date}</time>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
// 빌드 시 실행: 어떤 페이지들을 생성할지
export async function getStaticPaths() {
// 모든 포스트 목록 가져오기
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
// 생성할 경로 목록
const paths = posts.map(post => ({
params: { slug: post.slug }
}));
return {
paths,
fallback: 'blocking' // 새 페이지는 SSR로 처리
};
}
// 빌드 시 실행: 각 페이지의 데이터
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.slug}`);
const post = await res.json();
return {
props: {
post
},
revalidate: 60 // ISR: 60초마다 재생성
};
}
export default BlogPost;
// 빌드 결과:
// .next/server/pages/blog/first-post.html
// .next/server/pages/blog/second-post.html
// .next/server/pages/blog/third-post.html
// 장점:
// 1. 초고속 로딩
// 2. 서버 부하 없음
// 3. SEO 완벽
// 4. 저렴한 호스팅
// 단점:
// 1. 빌드 시간 오래 걸림
// 2. 실시간 데이터 X
// 3. 페이지 많으면 문제
// 적합한 경우:
// - 블로그
// - 문서 사이트
// - 마케팅 페이지
// - 포트폴리오
ISR (Incremental Static Regeneration)
// SSG + SSR의 장점 결합!
// pages/products/[id].js
function Product({ product }) {
return (
<div>
<h1>{product.name}</h1>
<p>가격: {product.price}원</p>
<p>재고: {product.stock}개</p>
</div>
);
}
export async function getStaticPaths() {
// 인기 상품 100개만 미리 생성
const popularProducts = await getPopularProducts(100);
return {
paths: popularProducts.map(p => ({
params: { id: p.id.toString() }
})),
fallback: 'blocking' // 나머지는 요청 시 생성
};
}
export async function getStaticProps({ params }) {
const product = await getProduct(params.id);
return {
props: { product },
revalidate: 10 // 10초마다 재검증
};
}
export default Product;
// 동작 방식:
// 1. 빌드 시: 인기 상품 100개 HTML 생성
// 2. 첫 요청: 나머지 상품은 SSR로 생성 후 캐시
// 3. 10초 후: 백그라운드에서 재생성
// 4. 항상 최신 데이터 유지!
// 장점:
// - 빠른 빌드 시간
// - 빠른 응답 속도
// - 최신 데이터 제공
// - 무한한 페이지 가능
하이브리드 예시
// Next.js 앱에서 여러 방식 혼합
// 1. 홈페이지 - SSG (정적)
// pages/index.js
export async function getStaticProps() {
return {
props: { hero: '...' },
revalidate: 3600 // 1시간마다
};
}
// 2. 블로그 목록 - ISR
// pages/blog/index.js
export async function getStaticProps() {
const posts = await getPosts();
return {
props: { posts },
revalidate: 60 // 1분마다
};
}
// 3. 블로그 포스트 - SSG
// pages/blog/[slug].js
export async function getStaticPaths() {
const posts = await getPosts();
return {
paths: posts.map(p => ({ params: { slug: p.slug }})),
fallback: 'blocking'
};
}
export async function getStaticProps({ params }) {
const post = await getPost(params.slug);
return {
props: { post },
revalidate: 3600
};
}
// 4. 사용자 대시보드 - CSR
// pages/dashboard.js
function Dashboard() {
const [data, setData] = useState(null);
useEffect(() => {
// 클라이언트에서만 실행
fetch('/api/user/dashboard')
.then(res => res.json())
.then(setData);
}, []);
return <div>{/* 대시보드 UI */}</div>;
}
// 5. 검색 결과 - SSR
// pages/search.js
export async function getServerSideProps({ query }) {
const results = await search(query.q);
return {
props: { results, query: query.q }
};
}
// 각 페이지에 최적의 렌더링 방식 적용!
SEO 비교
<!-- CSR: 검색엔진이 보는 것 (JavaScript 실행 전) -->
<!DOCTYPE html>
<html>
<head>
<title>React App</title>
</head>
<body>
<div id="root"></div>
<!-- 내용 없음! -->
<script src="/static/js/main.js"></script>
</body>
</html>
<!-- 검색엔진은 빈 페이지로 인식 ❌ -->
<!-- SSR/SSG: 검색엔진이 보는 것 -->
<!DOCTYPE html>
<html>
<head>
<title>최고의 리액트 튜토리얼</title>
<meta name="description" content="React를 배우는 가장 쉬운 방법">
<meta property="og:title" content="최고의 리액트 튜토리얼">
<meta property="og:image" content="https://example.com/og.jpg">
</head>
<body>
<div id="__next">
<article>
<h1>최고의 리액트 튜토리얼</h1>
<p>React를 배우는 가장 쉬운 방법을 소개합니다...</p>
<section>
<h2>1. 리액트란?</h2>
<p>리액트는 사용자 인터페이스를 만들기 위한...</p>
</section>
</article>
</div>
</body>
</html>
<!-- 완전한 내용이 있음! ✅ -->
<!-- Google, 네이버가 잘 인덱싱 -->
<!-- 소셜 미디어 미리보기 동작 -->
성능 비교
// 웹 성능 지표
// CSR (Create React App)
const csrMetrics = {
FCP: '2.5s', // First Contentful Paint
LCP: '3.5s', // Largest Contentful Paint
TTI: '4.0s', // Time to Interactive
TBT: '500ms', // Total Blocking Time
CLS: '0.1', // Cumulative Layout Shift
// 사용자 경험:
// 0-2.5s: 흰 화면
// 2.5-4s: 로딩 스피너
// 4s: 완전히 사용 가능
};
// SSR (Next.js)
const ssrMetrics = {
FCP: '0.8s', // ⬆️ 빠름!
LCP: '1.2s', // ⬆️ 빠름!
TTI: '2.5s', // ⬆️ 빠름!
TBT: '300ms', // ⬆️ 낮음!
CLS: '0.05', // ⬆️ 낮음!
// 사용자 경험:
// 0-0.8s: 로딩
// 0.8-2.5s: 볼 수 있지만 클릭 안 됨
// 2.5s: 완전히 사용 가능
};
// SSG (Next.js)
const ssgMetrics = {
FCP: '0.3s', // ⬆️⬆️ 매우 빠름!
LCP: '0.5s', // ⬆️⬆️ 매우 빠름!
TTI: '1.5s', // ⬆️⬆️ 매우 빠름!
TBT: '100ms', // ⬆️⬆️ 매우 낮음!
CLS: '0.02', // ⬆️⬆️ 매우 낮음!
// 사용자 경험:
// 0-0.3s: 로딩
// 0.3-1.5s: 볼 수 있지만 클릭 안 됨
// 1.5s: 완전히 사용 가능
};
// 결론:
// SSG > SSR > CSR (초기 로딩 속도)
// CSR = SSR = SSG (이후 네비게이션)
🤔 자주 묻는 질문
Q1. 어떤 렌더링 방식을 선택해야 하나요?
A: 프로젝트 특성에 따라 선택하세요:
// 선택 가이드
// 1. CSR을 선택하세요:
const csrUseCases = {
conditions: [
'SEO가 중요하지 않음',
'로그인 후 사용하는 앱',
'실시간 인터랙션 많음',
'서버 비용 절약'
],
examples: [
'관리자 대시보드',
'채팅 애플리케이션',
'게임',
'디자인 툴 (Figma 등)',
'음악 플레이어',
'할 일 관리 앱'
],
framework: 'Create React App, Vite'
};
// 2. SSR을 선택하세요:
const ssrUseCases = {
conditions: [
'SEO 중요',
'실시간 데이터 필요',
'사용자별 맞춤 콘텐츠',
'빠른 초기 로딩'
],
examples: [
'뉴스 사이트',
'소셜 미디어',
'e커머스 상품 페이지',
'검색 결과 페이지',
'실시간 주식 정보',
'날씨 앱'
],
framework: 'Next.js, Nuxt.js, SvelteKit'
};
// 3. SSG를 선택하세요:
const ssgUseCases = {
conditions: [
'SEO 중요',
'콘텐츠 자주 안 바뀜',
'최고 성능 필요',
'저렴한 호스팅'
],
examples: [
'블로그',
'문서 사이트',
'마케팅 랜딩 페이지',
'포트폴리오',
'회사 소개',
'제품 카탈로그'
],
framework: 'Next.js, Gatsby, Astro'
};
// 4. 하이브리드 (권장!):
const hybridUseCases = {
strategy: '페이지마다 다른 방식 사용',
example: {
'/': 'SSG', // 홈페이지
'/about': 'SSG', // 소개
'/blog': 'ISR', // 블로그 목록
'/blog/[slug]': 'SSG', // 블로그 포스트
'/products': 'ISR', // 상품 목록
'/products/[id]': 'ISR', // 상품 상세
'/search': 'SSR', // 검색 결과
'/dashboard': 'CSR', // 대시보드
'/profile': 'CSR' // 프로필
},
framework: 'Next.js (최고의 선택)'
};
// 의사결정 트리:
function chooseRenderingMethod(project) {
// SEO 필요?
if (!project.needsSEO) {
return 'CSR';
}
// 실시간 데이터?
if (project.needsRealtime) {
return 'SSR';
}
// 콘텐츠 자주 바뀜?
if (project.contentChangesOften) {
return 'ISR'; // SSG + 재생성
}
// 정적 콘텐츠
return 'SSG';
}
Q2. Next.js는 어떤 렌더링 방식인가요?
A: Next.js는 모든 방식을 지원합니다:
// Next.js - 하이브리드 프레임워크
// 1. SSG (기본값)
// pages/about.js
function About() {
return <div>회사 소개</div>;
}
export default About;
// getStaticProps 없으면 자동으로 SSG
// 2. SSG with Data
// pages/blog/[slug].js
export async function getStaticPaths() {
return {
paths: [{ params: { slug: 'hello' }}],
fallback: false
};
}
export async function getStaticProps({ params }) {
const post = await getPost(params.slug);
return { props: { post }};
}
// 3. ISR (Incremental Static Regeneration)
export async function getStaticProps() {
return {
props: { data: '...' },
revalidate: 60 // 60초마다 재생성
};
}
// 4. SSR
// pages/news.js
export async function getServerSideProps() {
const news = await getLatestNews();
return { props: { news }};
}
// 5. CSR
// pages/dashboard.js
function Dashboard() {
const { data } = useSWR('/api/user', fetcher);
return <div>{data}</div>;
}
// 6. API Routes (서버리스 함수)
// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({ message: 'Hello' });
}
// Next.js의 장점:
// - 파일 기반 라우팅
// - 자동 코드 스플리팅
// - 이미지 최적화
// - TypeScript 지원
// - 빠른 Refresh
// - Vercel 배포 최적화
Q3. Hydration이란?
A: SSR/SSG에서 정적 HTML을 인터랙티브하게 만드는 과정입니다:
// Hydration 과정
// 1단계: 서버에서 HTML 생성
// server.js
const html = ReactDOMServer.renderToString(<App />);
// 생성된 HTML:
<div id="root">
<button>클릭 (0)</button>
</div>
// 2단계: 브라우저에 전달
// 사용자가 즉시 볼 수 있음!
// 하지만 버튼 클릭 안 됨 (이벤트 리스너 없음)
// 3단계: JavaScript 로드 및 실행
// client.js
ReactDOM.hydrate(<App />, document.getElementById('root'));
// Hydration 중:
// 1. React가 DOM 트리 분석
// 2. Virtual DOM과 비교
// 3. 이벤트 리스너 연결
// 4. 상태 초기화
// 4단계: Hydration 완료
// 이제 버튼 클릭 가능! 인터랙티브함!
// Hydration 문제:
// 불일치 경고
const MismatchComponent = () => {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
return (
<div>
{/* ❌ 서버: "서버", 클라이언트: "클라이언트" */}
{typeof window === 'undefined' ? '서버' : '클라이언트'}
{/* ✅ Hydration 후에만 렌더링 */}
{mounted && <ClientOnlyComponent />}
</div>
);
};
// Hydration 최적화:
// 1. 초기 HTML 크기 줄이기
// 2. JavaScript 번들 크기 줄이기
// 3. Critical CSS 인라인
// 4. 필요한 부분만 Hydrate (Progressive Hydration)
Q4. CSR의 SEO 문제를 해결할 수 있나요?
A: 몇 가지 방법이 있지만 완벽하지 않습니다:
// CSR SEO 개선 방법
// 1. Prerendering (빌드 시 HTML 생성)
// react-snap 사용
// package.json
{
"scripts": {
"postbuild": "react-snap"
}
}
// 크롤러 봇이 방문하면 미리 생성된 HTML 제공
// 실제 사용자는 CSR로 동작
// 2. Server-Side Rendering Service
// Prerender.io, Rendertron 같은 서비스 사용
// nginx 설정
location / {
if ($http_user_agent ~* "googlebot|bingbot|yandex") {
proxy_pass http://prerender-service;
}
}
// 3. Google Search Console
// - JavaScript 렌더링 테스트
// - robots.txt 확인
// - sitemap.xml 제출
// 4. Meta 태그 동적 업데이트
import { Helmet } from 'react-helmet';
function BlogPost({ post }) {
return (
<>
<Helmet>
<title>{post.title}</title>
<meta name="description" content={post.excerpt} />
<meta property="og:title" content={post.title} />
<meta property="og:image" content={post.image} />
</Helmet>
<article>{/* ... */}</article>
</>
);
}
// 5. Structured Data (JSON-LD)
<script type="application/ld+json">
{`
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "${post.title}",
"author": "${post.author}",
"datePublished": "${post.date}"
}
`}
</script>
// 한계:
// - 완벽한 SEO는 불가능
// - 복잡하고 유지보수 어려움
// - SSR/SSG가 더 나음
// 결론: SEO 중요하면 SSR/SSG 사용!
Q5. 렌더링 성능을 측정하는 방법은?
A: 여러 도구로 측정할 수 있습니다:
// 1. Lighthouse (Chrome DevTools)
// 크롬 DevTools > Lighthouse 탭
// - Performance
// - Accessibility
// - Best Practices
// - SEO
// 주요 지표:
const webVitals = {
FCP: 'First Contentful Paint', // 첫 콘텐츠 표시
LCP: 'Largest Contentful Paint', // 최대 콘텐츠 표시
FID: 'First Input Delay', // 첫 입력 지연
TTI: 'Time to Interactive', // 인터랙티브까지
TBT: 'Total Blocking Time', // 총 차단 시간
CLS: 'Cumulative Layout Shift' // 누적 레이아웃 이동
};
// 2. web-vitals 라이브러리
import { getCLS, getFID, getFCP, getLCP, getTTI } from 'web-vitals';
function sendToAnalytics({ name, value, id }) {
console.log(name, value);
// Google Analytics로 전송
gtag('event', name, {
event_category: 'Web Vitals',
value: Math.round(value),
event_label: id,
non_interaction: true
});
}
getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getFCP(sendToAnalytics);
getLCP(sendToAnalytics);
getTTI(sendToAnalytics);
// 3. Next.js Analytics
// pages/_app.js
export function reportWebVitals(metric) {
console.log(metric);
// {
// name: 'FCP',
// value: 1234.5,
// id: 'v2-1234567890'
// }
}
// 4. Chrome DevTools Performance
// 1. DevTools > Performance 탭
// 2. 녹화 시작
// 3. 페이지 새로고침
// 4. 녹화 중지
// 5. 분석:
// - Loading: HTML, CSS, JS 다운로드
// - Scripting: JavaScript 실행
// - Rendering: 레이아웃, 페인트
// - Painting: 픽셀 그리기
// 5. Network 탭
// - 리소스 로딩 시간
// - 파일 크기
// - 워터폴 차트
// - 병렬 다운로드
// 6. React DevTools Profiler
import { Profiler } from 'react';
function onRenderCallback(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) {
console.log(`${id} (${phase}) took ${actualDuration}ms`);
}
<Profiler id="App" onRender={onRenderCallback}>
<App />
</Profiler>
// 목표 지표:
const goodMetrics = {
FCP: '< 1.8s',
LCP: '< 2.5s',
FID: '< 100ms',
TTI: '< 3.8s',
TBT: '< 200ms',
CLS: '< 0.1'
};
🎓 다음 단계
렌더링 방식을 이해했다면, 다음을 학습해보세요:
- Virtual DOM이란? - React 렌더링의 핵심 원리
- 웹 성능 최적화 (문서 작성 예정) - 성능 개선 방법
- SEO 기초 (문서 작성 예정) - 검색 엔진 최적화
🎬 마무리
렌더링 방식은 웹 애플리케이션의 성능과 SEO에 큰 영향을 미칩니다:
- CSR: 풍부한 인터랙션, SEO 어려움
- SSR: 빠른 초기 로딩, 서버 비용 높음
- SSG: 최고 성능, 정적 콘텐츠에 적합
- ISR: SSG + 자동 업데이트, 최고의 조합
프로젝트 특성에 맞는 렌더링 방식을 선택하고, 필요하다면 하이브리드로 조합하세요!