블로그 프로젝트에 ISR(Incremental Static Regeneration)을 적용하면서 배운 것들을 정리했다.
왜 ISR인가?
블로그 특성상 콘텐츠가 자주 바뀌지 않는다. 하지만:
- SSG: 새 글 반영이 느림 (빌드 필요)
- SSR: 매 요청마다 서버 부하
ISR로 둘 다 해결:
- 정적 페이지의 빠른 로딩
- 주기적 재검증으로 콘텐츠 자동 갱신
페이지별 revalidate 전략
모든 페이지에 같은 값을 적용하는 게 아니라, 콘텐츠 특성에 맞게 다르게 설정했다.
// 홈페이지 - 40초 export const revalidate = 40; // 아카이브 목록 - 60초 export const revalidate = 60; // 포스트 상세 - 1시간 export const revalidate = 3600;
이유:
- 홈 (40초): 최신 포스트가 빠르게 보여야 함
- 목록 (60초): 목록은 자주 바뀌지 않음
- 상세 (1시간): 작성된 글은 거의 수정 없음
SSG + ISR 하이브리드
포스트 상세는 빌드 시 미리 생성 + ISR로 갱신하는 방식을 썼다.
// src/app/archive/[id]/page.tsx export async function generateStaticParams() { const supabase = createStaticClient(); const { data: posts } = await supabase .from('posts') .select('id'); return posts?.map((post) => ({ id: String(post.id), })) ?? []; } export const revalidate = 3600;
이렇게 하면:
- 빌드 시 기존 포스트들은 정적 HTML로 생성
- 새 포스트는 첫 요청 시 생성 후 캐시
- 1시간마다 재검증
핵심: Static Client 분리
여기서 삽질을 많이 했다. cookies를 사용하면 ISR이 작동하지 않는다.
// ❌ 이렇게 하면 동적 렌더링됨 export function createServerClient() { const cookieStore = cookies(); // Dynamic! return createServerClient(/* ... */); }
cookies()를 호출하는 순간 Next.js는 이 페이지를 동적으로 판단한다.
해결책: Static Client 분리
// src/lib/supabase/server.ts // 인증 필요한 경우 (SSR) export function createServerClient() { const cookieStore = cookies(); return createServerClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, { cookies: /* ... */ } ); } // 공개 데이터만 조회 (ISR/SSG 가능) export function createStaticClient() { return createClient( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! ); }
사용 구분:
createServerClient: 로그인 상태, 사용자별 데이터
createStaticClient: 공개 포스트, 목록 조회
정리
페이지 | revalidate | Client | 이유 |
홈 | 40초 | Static | 최신 글 빠른 반영 |
아카이브 | 60초 | Static | 목록 안정적 |
포스트 상세 | 3600초 | Static | 거의 수정 없음 |
관리자 | - | Server | 인증 필요 |
ISR을 쓰려면 cookies를 피해야 한다는 게 핵심이었다.


