프론트엔드 렌더링·캐싱·Hydration 설계 가이드
이 글은 기술 이름 나열보다, 실제로 큰 프론트엔드 시스템을 운영할 때 어떤 기준으로 SSR, SSG, ISR, RSC, hydration boundary, 브라우저 캐시, CDN 캐시, 데이터 캐시를 조합해야 하는지에 초점을 둔다.
먼저 풀어야 할 질문
렌더링 전략은 프레임워크 취향이 아니라 다음 질문에 대한 답이다.
- 첫 화면에서 반드시 보여야 하는 정보는 무엇인가
- SEO가 필요한가, 아니면 로그인 후 화면인가
- 데이터가 얼마나 자주 바뀌는가
- 사용자별 개인화 비율이 얼마나 높은가
- hydration 없이 읽기만 가능한가, 즉시 상호작용이 필요한가
- 서버 비용보다 사용자 체감 속도가 더 중요한가
같은 Next.js나 Nuxt를 쓰더라도 이 답이 다르면 정답도 달라진다.
구조를 레이어로 봐야 한다
[Origin App]
|
+--> HTML generation: SSR / SSG / ISR / RSC
|
+--> Data cache: fetch cache / query cache / KV / DB read model
|
v
[CDN Cache]
|
v
[Browser Cache]
|
+--> HTML
+--> JS/CSS assets
+--> API responses
|
v
[Hydration + Client State]
실무에서 흔한 실수는 이 레이어를 섞는 것이다. 예를 들어 CDN 캐시를 못 쓰는 개인화 페이지인데도 HTML 캐시 전략만 논의하거나, 데이터 캐시로 해결할 문제를 브라우저 hydration 최적화 문제로 착각한다.
SSR, SSG, ISR, RSC를 역할 기준으로 구분하기
SSR
SSR은 요청 시점에 HTML을 만든다. 로그인 기반 개인화, 지역/권한별 분기, SEO가 필요한 동적 페이지에 적합하다. 하지만 요청마다 서버 실행 비용이 생기고, 캐시가 약하면 TTFB가 금방 나빠진다.
SSG
SSG는 빌드 시점 HTML 생성이다. 마케팅 페이지, 문서, 변경 빈도가 낮은 콘텐츠에 강하다. 가장 싸고 단순하지만 콘텐츠 갱신 요구가 높아지면 빌드 병목이 생긴다.
ISR
ISR은 SSG와 SSR 사이의 운영 타협이다. 정적 페이지를 기본으로 두되, 재생성 주기를 두거나 on-demand revalidation을 건다. 카탈로그형 콘텐츠에 잘 맞지만, “조금 동적”인 모든 페이지에 습관적으로 붙이면 오히려 무효화 설계가 복잡해진다.
RSC
RSC는 서버에서 렌더링된 컴포넌트를 클라이언트 번들에서 분리하는 모델이다. 장점은 데이터 접근과 무거운 렌더 계산을 서버에 두고, 클라이언트 JS 양과 hydration 범위를 줄일 수 있다는 점이다. 하지만 상호작용 경계를 잘못 잡으면 오히려 서버/클라이언트 책임이 불투명해진다.
Hydration 경계는 UI 경계가 아니라 비용 경계다
Hydration은 정적 HTML을 인터랙티브 UI로 연결하는 과정이다. 문제는 많은 팀이 이 경계를 페이지 구조와 동일하게 두고 있다는 점이다. 큰 페이지를 통째로 hydration하면 초기 JS 비용, 메인 스레드 점유, 이벤트 바인딩 비용이 한꺼번에 몰린다.
좋은 기준은 다음과 같다.
- 읽기 전용 콘텐츠는 가급적 hydration하지 않는다.
- 상호작용이 필요한 작은 섬만 client boundary로 둔다.
- above-the-fold 상호작용 요소를 우선 hydration한다.
- 검색, 차트, 편집기처럼 무거운 위젯은 늦게 로드한다.
[Server Content]
|- article body -> server only
|- product summary -> server only
|- add-to-cart button -> client boundary
|- reviews filter panel -> client boundary
|- recommendation carousel -> deferred client boundary
Hydration boundary를 작게 만들수록 무조건 좋다는 뜻은 아니다. 너무 잘게 쪼개면 데이터 흐름과 번들 조각 관리가 어려워진다. 핵심은 JS를 보내야 하는 이유가 분명한 곳에만 JS를 보내는 것이다.
캐시는 한 종류가 아니다
1. 브라우저 자산 캐시
해시된 JS/CSS는 강하게 캐시해야 한다. 여기는 거의 논쟁이 없다. immutable 자산은 오래 캐시하고 파일명 해시로 무효화하는 것이 표준이다.
2. 브라우저 API 캐시
브라우저 캐시는 로그인 상태, 민감 데이터, 만료 규칙에 따라 매우 조심해야 한다. 잘못된 Cache-Control은 개인정보 노출로 이어진다.
3. CDN HTML 캐시
공용 페이지에서 매우 강력하다. 다만 사용자 쿠키나 지역화, A/B 테스트가 섞이면 캐시 키 설계가 먼저다. 캐시 적중률보다 캐시 오염 방지가 우선이다.
4. 서버 데이터 캐시
RSC나 SSR 내부에서 fetch 결과를 재사용하는 계층이다. DB 조회, 내부 API 호출, CMS 응답을 캐시할 수 있다. HTML 캐시가 불가능한 개인화 페이지에서도 이 계층은 효과적이다.
5. 클라이언트 데이터 캐시
TanStack Query 같은 캐시는 hydration 이후의 재방문, 탭 이동, optimistic UI에 유리하다. 하지만 서버가 이미 잘 전달한 데이터를 클라이언트에서 다시 즉시 refetch하면 이점이 사라진다.
페이지 유형별 전략 예시
콘텐츠 페이지
- 기본: SSG 또는 ISR
- CDN 캐시 적극 사용
- 본문은 server-only
- 댓글, 북마크 같은 기능만 client boundary
상품 상세
- 기본: SSR 또는 ISR + 세분화된 data cache
- 가격/재고는 짧은 TTL 또는 별도 API
- 추천/리뷰 필터는 hydration 지연 가능
로그인 후 대시보드
- 기본: SSR 또는 RSC 기반 개인화
- HTML 공용 캐시 거의 불가
- 대신 서버 데이터 캐시와 client query cache를 조합
- 차트, 테이블, 편집기는 선택적 hydration
검색 결과
- SEO 랜딩은 SSR
- 이후 필터 변경은 client data cache 활용
- 결과 영역과 필터 UI의 hydration 우선순위를 분리
큰 시스템에서 흔한 안티패턴
- 모든 페이지를 SSR로 통일해 운영비를 폭증시킴
- 모든 페이지를 SPA처럼 hydration해서 메인 스레드를 잠금
- 개인화 페이지인데 CDN 캐시로 해결하려고 함
- 서버가 만든 데이터를 클라이언트가 mount 직후 다시 가져옴
- ISR을 쓰면서 무효화 전략은 정의하지 않음
- RSC를 썼지만 결국 모든 하위 컴포넌트를 client component로 만듦
기술 선택이 아니라 경계 설계 실패다.
의사결정 가이드
If SEO + low change frequency -> SSG first
If SEO + medium change frequency -> ISR or SSR with CDN strategy
If per-user personalization -> SSR/RSC, cache data not HTML
If interaction is optional -> delay hydration
If page is read-heavy and stable -> push work to build/CDN
If page is user-specific and mutable -> push work to server data cache
예시 구조
// Product page shell
export default async function ProductPage({ params }) {
const product = await getProduct(params.id); // server cacheable
const inventory = await getInventory(params.id); // short TTL
return (
<>
<ProductSummary product={product} inventory={inventory} />
<AddToCartClient productId={params.id} />
<Suspense fallback={<ReviewsSkeleton />}>
<ReviewsServerSection productId={params.id} />
</Suspense>
<DeferredRecommendationsClient productId={params.id} />
</>
);
}
여기서 중요한 것은 모든 요소를 같은 렌더링 전략으로 처리하지 않는다는 점이다.
운영 체크리스트
- 페이지별로 SEO, 개인화, 갱신 빈도, 상호작용 수준이 분류돼 있는가
- HTML 캐시와 데이터 캐시를 분리해 설계했는가
- hydration 대상 컴포넌트가 명시적으로 관리되는가
- 서버가 준 데이터를 클라이언트가 즉시 중복 요청하지 않는가
- CDN 캐시 키에 언어, 지역, 실험군, 로그인 여부가 반영되는가
- 캐시 무효화 이벤트가 운영 절차로 정의돼 있는가
- TTFB, LCP, INP, hydration error를 함께 관측하는가
마무리
대규모 프론트엔드에서 좋은 렌더링 전략은 “우리 팀은 SSR을 쓴다” 같은 슬로건이 아니다. 어떤 화면은 CDN에 맡기고, 어떤 데이터는 서버 캐시에 맡기고, 어떤 상호작용만 hydration할지 결정하는 경계 설계다. 프레임워크 기능은 많지만, 성능과 운영비를 좌우하는 것은 결국 어디서 계산하고 어디서 캐시하며 어디까지 JS를 보내는지에 대한 판단이다.
운영 환경에서 어려워지는 지점
- 렌더링 전략, 캐시 전략, hydration 비용은 강하게 연결되어 있지만 팀은 자주 따로 최적화한다.
- HTML 전달이 빨라도 hydration이 상호작용을 막거나 데이터가 바로 stale해지면 페이지는 여전히 느리게 느껴진다.
- 가장 어려운 버그는 대개 캐시 수명과 사용자 기대가 맞지 않을 때 나온다.
중요한 아키텍처 결정
- route 계열별로 개인화, 신선도, 상호작용 긴급성 기준에 따라 렌더링 모드를 고른다.
- CDN부터 브라우저까지 캐시 계층의 소유권과 무효화 규칙을 명시적으로 설계한다.
- 상호작용이 없는 영역은 서버 또는 정적으로 유지해 hydration 범위를 줄인다.
실무 예시
실무에서는 즉흥 결정 대신 route 정책 매트릭스로 정리하는 편이 낫다.
마케팅 페이지 -> 정적 + 긴 CDN 캐시
카탈로그 페이지 -> 서버 렌더 + 재검증 윈도우
사용자 대시보드 -> 개인화 SSR + 짧은 데이터 캐시
관리 도구 -> 클라이언트 비중 높음 + 보호된 API 캐시
피해야 할 안티패턴
- SSR을 모든 곳에 적용하고 그것만으로 성능이 해결된다고 보는 것.
- 의미 있는 상호작용이 없는 마케팅 섹션까지 크게 hydration하는 것.
- 비즈니스 기준의 신선도 예산 없이 과감한 캐시를 거는 것.
운영 체크리스트
- TTFB, LCP, TTI를 함께 측정한다.
- hydration 오류율과 stale data 사고를 추적한다.
- route handler와 API 전반의 cache key 전략을 문서화한다.
- cold cache와 warm cache 상황을 분리해서 테스트한다.
최종 판단
올바른 렌더링 전략은 신선도와 상호작용 요구에 맞는 전략이다. 캐시와 hydration은 부가 주제가 아니라 그 선택이 실제로 통하는지 결정하는 핵심 요소다.
Continue Reading
다음으로 읽기 좋은 글
프론트엔드 Partial Hydration 경계 설계
모든 컴포넌트를 한 번에 깨우기보다, 어디까지 상호작용이 필요한지 경계를 나누는 것이 프론트엔드 성능 최적화의 핵심입니다.
🖥️ FrontendVue + SSR 아키텍처 가이드
Vue 기반 SSR 애플리케이션을 언제 도입해야 하는지, SPA와 무엇이 달라지는지, 데이터 로딩과 캐싱, hydration, 배포 모델까지 실무 관점에서 정리합니다.
📈 최신 동향React Foundation 이 엔지니어링 팀에 의미하는 것
React Foundation 소식이 단순 거버넌스 뉴스가 아니라 프레임워크 생태계의 장기 예측 가능성에 어떤 의미를 갖는지 정리한 글입니다.
⚙️ BackendSpring Boot + Redis 캐싱 전략 실전 가이드
@Cacheable 사용을 넘어서 TTL, 무효화, hot key, 일관성 트레이드오프, 운영 지표까지 Redis 캐싱을 실무 관점에서 정리합니다.
다음 탐색