핵심 요약 (Summary)
SSG, SSR, ISR 중 무엇을 선택할지는 기술 스펙 비교표보다 "이 콘텐츠가 얼마나 자주 바뀌는가", "얼마나 빠르게 반영되어야 하는가", "얼마나 많은 사람이 동시에 접근하는가" 세 가지 질문으로 결정됩니다. 골프장 홈페이지를 만들면서 이 세 질문을 제대로 따지지 않았을 때 어떤 일이 벌어지는지, 그리고 어떻게 해결했는지를 기록합니다.
프로젝트 배경 (Context)
골프장 홈페이지 프로젝트를 맡았을 때 처음엔 별로 복잡할 게 없어 보였습니다. 코스 소개, 이용 요금, 예약 안내, 공지사항, 이벤트 게시판. 전형적인 기업 홈페이지 구조였습니다.
Next.js App Router를 쓰기로 했고, 첫 설계에서는 고민 없이 전체를 SSG로 잡았습니다. generateStaticParams로 모든 페이지를 빌드 시점에 미리 만들어두고 CDN에 올리면 빠르고 안정적이라고 생각했습니다.
문제는 오픈 이후 두 번째 주에 터졌습니다.
첫 번째 문제 — 공지사항이 반영되지 않는다
운영팀에서 갑작스러운 코스 점검으로 오전 라운딩이 취소됐다는 공지를 올렸습니다. CMS에는 분명히 게시가 됐는데, 실제 홈페이지에는 반영이 안 됐습니다. 문의가 들어오기 시작했습니다.
SSG로 만든 페이지는 빌드 시점의 스냅샷입니다. CMS에서 글을 새로 올려도, 이미 생성된 HTML 파일은 그 사실을 모릅니다. 공지사항 페이지를 업데이트하려면 전체 재배포가 필요했습니다. 배포 파이프라인을 타면 빠르게는 5분, 느리게는 15분 이상 걸렸습니다.
긴급 공지가 15분 후에 뜨는 홈페이지. 이건 그냥 느린 게 아니라 사용자에게 잘못된 정보를 제공하는 문제였습니다.
이 시점에서 "전체 SSG"라는 처음 결정을 재검토했습니다.
콘텐츠별로 다르게 생각해야 한다는 것을 깨달은 순간
문제를 들여다보면서 이 홈페이지 안에 성격이 전혀 다른 콘텐츠들이 섞여 있다는 걸 알게 됐습니다.
코스 소개, 그린 피 안내, 시설 정보 같은 콘텐츠는 한번 만들면 수개월에서 수년이 지나도 바뀌지 않습니다. 이런 페이지에 SSG는 최선의 선택입니다. 빠르고, 서버 부하가 없고, CDN에서 바로 응답합니다.
이벤트 게시판은 다릅니다. 시즌 이벤트, 멤버십 할인 행사, 라운딩 패키지 같은 글들이 올라옵니다. 성수기(4월6월, 9월10월)에는 하루에 한두 개씩 게시되기도 합니다. 하지만 이벤트는 당장 몇 분 안에 사이트에 올라와야 할 만큼 긴급한 성격은 아닙니다. 행사 시작 30분 전에 게시됐다가 30분 후에 반영돼도 큰 문제는 없습니다.
공지사항은 전혀 다른 이야기입니다. 갑작스러운 코스 점검, 기상 악화로 인한 운영 취소, 주차장 통제. 이런 공지는 올라가는 즉시 사용자가 볼 수 있어야 합니다. 골프장에 도착해서 공지를 확인했는데 30분 전에 올라온 운영 취소 공지를 아직 못 봤다는 상황은 심각한 UX 실패입니다.
이 세 가지가 같은 홈페이지 안에 있지만, 렌더링 전략은 달라야 했습니다.
SSG는 언제 쓰는가
변경 가능성이 거의 없고, 내용이 바뀌더라도 즉각적인 반영이 필요하지 않은 콘텐츠에 씁니다.
골프장 홈페이지에서는 코스 소개 페이지, 이용 안내, 오시는 길, 그린피 안내(요금 변동이 시즌 단위)가 여기에 해당합니다. 사용자가 페이지를 열었을 때 서버 연산이 전혀 없이 CDN에서 바로 HTML이 내려옵니다. 응답 속도가 가장 빠르고 서버 비용도 없습니다.
성수기에 수백 명이 동시에 코스 안내 페이지를 열어도 서버는 아무 일도 하지 않습니다. 그냥 저장된 파일을 주는 것이니까요.
단, 이 방식을 쓰려면 콘텐츠 담당자가 "이 페이지는 수정 후 배포를 눌러야 반영된다"는 걸 이해해야 합니다. 프로세스 정의가 기술만큼 중요합니다.
SSR은 언제 쓰는가
요청이 들어올 때마다 서버에서 HTML을 새로 만들어야 하는 콘텐츠에 씁니다. 즉, 같은 URL로 접속해도 사람마다 또는 시간마다 다른 내용을 보여줘야 할 때입니다.
골프장 홈페이지에서는 당일 잔여 티타임 조회, 회원 로그인 후 보이는 예약 현황, 실시간 날씨 연동 배너가 여기에 해당합니다. 잔여 티타임은 누군가 예약을 완료한 순간 바뀌기 때문에 캐시된 HTML을 보여주면 안 됩니다.
SSR의 단점은 트래픽이 늘어날수록 서버 부하가 비례해서 증가한다는 점입니다. 성수기에 수백 명이 실시간으로 잔여 티타임을 새로고침하면, 그 횟수만큼 서버가 DB를 조회하고 HTML을 만듭니다. 이 부분에서는 캐싱 전략을 별도로 설계해야 합니다.
중요한 것은 SSR을 써야 하는 진짜 이유가 있을 때만 써야 한다는 점입니다. "혹시 데이터가 바뀔 수도 있으니까"라는 이유로 SSR을 쓰면, 실제로 변경이 거의 없는 페이지도 매 요청마다 서버 자원을 씁니다.
ISR이 이 프로젝트의 실질적인 해답이었다
ISR은 SSG처럼 HTML을 미리 만들어두지만, 설정한 시간이 지나면 백그라운드에서 조용히 페이지를 다시 만듭니다. 사용자는 항상 캐시된 빠른 응답을 받고, 콘텐츠는 일정 주기로 업데이트됩니다.
이벤트 게시판에 ISR을 적용했습니다. revalidate를 600초(10분)로 설정했습니다. 새 이벤트가 올라가면 최대 10분 후에 사이트에 반영됩니다. 이벤트 특성상 이 정도 지연은 문제가 없었습니다. 성수기에 트래픽이 몰려도 캐시된 HTML을 돌려주니 서버 부하도 없었습니다.
공지사항 목록 페이지에도 ISR을 적용했지만, revalidate를 60초로 짧게 잡았습니다. 새 공지가 올라오면 1분 안에 반영됩니다. 긴급 상황이 아닌 일반 공지라면 1분의 지연은 충분히 허용 가능했습니다.
revalidate 시간을 얼마로 잡을 것인가
ISR을 쓰기로 결정했다면, 다음 질문이 바로 이것입니다. "몇 초로 설정하지?"
짧을수록 콘텐츠가 빨리 반영되지만, 짧게 설정하면 캐시 효과가 줄어듭니다. 10초로 설정하면 10초마다 서버가 재생성 요청을 처리할 수 있습니다. 트래픽이 많을 때는 사실상 SSR과 비슷한 부하가 생깁니다.
길게 설정하면 서버 부하는 줄지만, 새 콘텐츠가 늦게 반영됩니다. 1시간으로 잡으면 방금 올린 이벤트가 1시간 후에나 사이트에 뜹니다.
이 프로젝트에서 결국 도달한 기준은 이것이었습니다.
콘텐츠의 긴급성이 낮고 트래픽이 높은 페이지 — revalidate를 길게 잡습니다. 성수기에 많이 접근하는 코스 소개, 요금 안내는 SSG로 아예 고정했습니다.
콘텐츠가 자주 바뀌고 즉각 반영이 중요하지 않은 페이지 — revalidate를 중간으로 잡습니다. 이벤트 게시판은 10분, 공지사항 목록은 1분으로 설정했습니다.
그리고 긴급 반영이 필요한 경우는 다른 방법을 써야 했습니다.
On-demand Revalidation — revalidate 시간이 답이 아닌 경우
"코스 임시 휴장" 공지가 올라갔습니다. revalidate를 60초로 설정했으니 1분 후에 반영됩니다. 하지만 운영팀은 "지금 당장 올라와야 한다"고 했습니다.
이 상황을 revalidate 숫자를 줄이는 것으로는 해결할 수 없습니다. 1초로 설정하면 매초 서버가 페이지를 재생성하는 말도 안 되는 상황이 됩니다.
Next.js에는 On-demand Revalidation이라는 기능이 있습니다. 특정 이벤트가 발생했을 때, 즉 CMS에서 공지사항이 새로 게시될 때, 코드로 "지금 이 페이지를 즉시 재생성해라"는 신호를 보낼 수 있습니다. revalidatePath나 revalidateTag를 Server Action이나 Route Handler 안에서 호출하는 방식입니다.
결국 구조를 이렇게 잡았습니다. 공지사항 페이지는 기본적으로 ISR로 동작하고 revalidate는 넉넉하게 잡습니다. CMS에서 공지가 게시되는 순간 웹훅으로 재생성 신호를 보냅니다. 일반적인 상황에서는 ISR의 캐싱 혜택을 누리고, 긴급한 상황에서는 즉시 반영됩니다.
성수기가 되니 달라 보이는 것들
봄 성수기(4~6월)에 접어들면서 트래픽이 눈에 띄게 늘었습니다. 이때 ISR의 진짜 가치를 실감했습니다.
이벤트 게시판에 하루에 두 개씩 새 이벤트가 올라오는 날에도, 서버 부하는 거의 변하지 않았습니다. 수백 명이 이벤트 페이지를 동시에 열어도 캐시된 HTML이 나가기 때문입니다. 같은 시간대에 티타임 잔여 조회(SSR)는 요청 수에 비례해서 부하가 올라갔습니다. 성수기에 SSR 페이지는 캐싱 전략을 별도로 보강해야 했습니다.
generateStaticParams로 모든 공지사항을 빌드 시점에 생성하던 방식도 바꿨습니다. 누적 공지사항이 수백 개가 되면 빌드 시간이 길어집니다. 최근 50개만 빌드 시점에 pre-generate하고, 나머지는 첫 요청 시 SSR로 생성 후 ISR 캐시에 저장하도록 변경했습니다. 배포 속도가 확연히 빨라졌습니다.
결국 질문은 세 가지였다
이 프로젝트를 마치고 나서, 렌더링 전략을 결정할 때 스스로에게 물어야 하는 질문을 정리했습니다.
첫 번째, 이 콘텐츠는 얼마나 자주 바뀌는가. 수개월에 한 번이면 SSG, 하루 한두 번이면 ISR, 매 요청마다 다르면 SSR입니다.
두 번째, 바뀐 내용이 얼마나 빨리 반영되어야 하는가. 몇 시간 이내면 ISR로 충분하고, 즉시 반영이 필요하면 On-demand Revalidation을 함께 써야 합니다. 매 요청마다 최신 데이터가 보장되어야 하면 SSR입니다.
세 번째, 동시에 얼마나 많은 사람이 접근하는가. 트래픽이 높을수록 캐싱의 가치가 커집니다. 고트래픽 페이지에 SSR을 쓰면 서버 비용이 직접 올라갑니다. SSG나 ISR로 처리할 수 있는 페이지는 그렇게 두는 것이 맞습니다.
이 세 가지 질문을 페이지별로 따져서 전략을 다르게 가져간 것이 이 프로젝트에서 가장 잘한 결정이었습니다. 처음에 "전체 SSG"로 시작했다가 공지사항 문제를 겪으면서 배운 것이기도 합니다.
정리 (Conclusion)
SSG, SSR, ISR은 우열이 없는 도구들입니다. 같은 홈페이지 안에서도 페이지마다 다른 전략을 쓰는 것이 정상입니다. 코스 소개는 SSG, 잔여 티타임은 SSR, 공지사항과 이벤트는 ISR. 이 조합이 이 프로젝트에서 성능과 운영 편의성을 동시에 잡은 방법이었습니다.
한 가지를 더 덧붙이자면, 렌더링 전략은 기술팀 혼자 결정할 게 아닙니다. "이 공지는 올리자마자 바로 떠야 해요"라는 운영팀의 요구사항이 On-demand Revalidation을 도입하게 만들었습니다. 비즈니스 요구사항이 곧 기술 선택의 기준이 됩니다.