728x90
서론
이번 글에서는 아직 다루지 않은 SSR(Server-Side Rendering) 페이지의 풀 페이지 캐싱에 대해 이야기합니다.
SSR은 매 요청마다 서버에서 HTML을 생성하므로 항상 최신 데이터를 보장하지만, 그만큼 성능 부담이 큽니다.
그렇다면 SSR 페이지를 캐싱하면서도 신선함을 유지할 수 있는 방법은 무엇일까요?
SSR의 본질 (Next.js 15 기준)
SSR이 하는 일
- 매 요청마다 서버에서 HTML 생성
- 데이터는 요청 시점에 가져옴
- SEO 친화적, 항상 최신 상태 반영
적합한 경우
- 자주 변하는 데이터
- 사용자별 맞춤형 페이지
- 인증 필요 페이지
성능 문제가 발생하는 경우
- 모든 요청이 DB/API 호출을 동반
- 고트래픽 시 서버 부하 급증
- TTFB(Time to First Byte) 지연
따라서 풀 페이지 캐싱은 필수적인 최적화 전략이 됩니다.
풀 페이지 캐싱이 필요한 경우
- 데이터 갱신 주기가 짧지만 실시간은 아님
예: 상품 상세 페이지, 블로그 상세, 마케팅 페이지 - 비로그인 대량 트래픽
로그인 상태가 필요 없는 페이지는 동일 캐시를 공유 가능 - 복잡한 백엔드 로직을 거치는 경우
- 글로벌 서비스
CDN과 엣지 캐싱을 활용해 전 세계 어디서나 빠른 응답 제공
캐싱하면 안 되는 경우:
실시간 주식 시세, 관리자 대시보드처럼 매 요청마다 다른 데이터가 필요한 경우
SSR 캐싱 전략
1. CDN + Cache-Control 헤더
// app/product/[slug]/page.tsx
import { headers } from 'next/headers';
import { fetchProductBySlug } from '@/lib/data';
export const dynamic = 'force-dynamic';
export default async function ProductPage({ params }) {
const { slug } = params;
const product = await fetchProductBySlug(slug);
const responseHeaders = headers();
responseHeaders.set(
'Cache-Control',
'public, s-maxage=60, stale-while-revalidate=120'
);
return (
<main>
<h1>{product.title}</h1>
<p>{product.description}</p>
<p>Price: ${product.price}</p>
</main>
);
}
s-maxage=60
: CDN에서 60초 동안 캐시 유지stale-while-revalidate=120
: 만료 후에도 최대 120초 동안은 오래된 버전을 즉시 제공하며 백그라운드에서 새로 갱신
👉 Vercel, Netlify 등 CDN 기반 배포 환경에서 가장 간단하고 효과적인 방법
2. Redis 등 외부 저장소 활용
- 요청 URL을 키로 사용
- 첫 요청 시 SSR 결과를 Redis에 저장
- TTL(Time to Live) 설정으로 신선도 유지
- 주로 커스텀 서버 환경에서 활용
3. Edge Middleware + 변형된 캐시 키
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export const config = { matcher: ['/product/:path*'] };
export function middleware(request: NextRequest) {
const geo = request.geo?.country || 'US';
const url = request.nextUrl.clone();
url.pathname = `/${geo.toLowerCase()}${url.pathname}`;
return NextResponse.rewrite(url, {
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=120',
},
});
}
👉 국가별, A/B 테스트, 언어별 변형된 페이지를 캐싱할 때 활용
4. ISR (Incremental Static Regeneration) 조합
// app/blog/[slug]/page.tsx
import { getPost } from '@/lib/posts';
export const revalidate = 60; // 60초마다 재생성
export default async function BlogPost({ params }) {
const post = await getPost(params.slug);
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
}
- 최초 요청 시 정적 페이지 제공
- 만료 후 첫 요청에서 새 버전 재생성 (백그라운드에서 처리)
👉 빠른 응답 + 일정 수준의 신선도 유지 가능
디버깅 방법 (Vercel 기준)
헤더 확인
curl -I https://your-domain.com/page
x-vercel-cache: HIT | MISS | STALE
지역별 테스트
VPN 또는 Vercel CLI 사용캐시 우회 요소 확인
- 세션 쿠키, force-dynamic 등
Analytics 활용
- 캐시 적중률, TTFB, 지역별 지연 시간 확인 가능
Best Practice
no-store
는 진짜 필요할 때만 사용stale-while-revalidate
적극 활용- 불필요한 쿠키 제거
- 캐시 주기는 실제 데이터 갱신 주기 기반으로 설정
- 캐시 변형은 꼭 필요한 수준까지만 (geo, 언어 등)
결론
SSR 페이지를 캐싱한다는 것은 모순처럼 보일 수 있지만, 사실은 대규모 트래픽을 안정적으로 처리하기 위한 핵심 전략입니다.
- Cache-Control 헤더
- Redis 등 외부 캐시
- Edge Middleware
- ISR 조합
을 적절히 활용하면, 최신성과 성능을 모두 잡는 SSR 페이지를 구현할 수 있습니다.
728x90
'Frontend Development' 카테고리의 다른 글
React 리렌더링(Re-rendering): Trigger → Render → Commit (0) | 2025.10.03 |
---|---|
실무에서 꼭 알아야 할 JWT 저장소 보안 패턴과 공격 탐지 방법 (0) | 2025.09.13 |
useEffect에서 setInterval이 상태를 못 따라오는 이유 (stale closure) (0) | 2025.09.07 |
React Error Boundary: 왜 아직도 클래스일까? (0) | 2025.09.07 |
setTimeout vs Promise.then vs queueMicrotask (0) | 2025.09.05 |