시작에 앞서
Next.js의 무언가를 새로 배울 때면, 항상
“”세상에 이런 프레임워크가...
라는 생각이 들었던 것 같다.
Next.js를 알만큼 안다고 생각할 때 항상 뭔가 튀어나왔다.
이런 궁금증들을 해결하면서 내 부족한 지식들을 채워나가다 보니, 요즘은 취미가 내가 뭘 모르는지 찾는 일인 것 같다.
SSG와 ISR이 딱 그랬던 것 같다.
사실 존재는 알고 있었지만, 한번도 써본적도, 제대로 공부해볼 시도조차 하지 않았다.
이 좋은걸 그동안 왜 안썼지 싶으면서도 Next.js 개발팀은 넘을 수 없는 벽이라는 생각이 든다.
공부를 하면서 나름대로 열심히 성장 중인듯 하지만
여전히 모르는 것이 훨씬 많고, 아마 이 간극은 쉽게 줄어들것 같지 않다.
하지만 이제는 그 사실이 불안보다는 이유가 되고 있는 듯 하다.
모르는 것을 마주하고, 이해하고, 내 것으로 만드는 과정 자체가
내가 이 프레임워크를 좋아하고 자주쓰는 이유인 것 같다.
서론이 길었으니 얼른 본론으로 가보자
Static Site Generation (신세계)
“”만약 페이지가 정적 생성을 사용한다면, 페이지 HTML은 빌드 타임에 생성됩니다.
Next.js 공식 문서에 적혀있는 문구이다.
예제
const Header = async () => {
const { data } = await subApiClient.get<Menu[]>("/").withSSG();
return (
<header className="max-w-5xl w-full py-8 mx-auto space-y-2">
...</header>
);
};위와 같이 컴포넌트 내에 데이터 페칭 로직이 있을 때,
const data = await fetch('https://api.example.com/a', {
cache: 'force-cache',
})cache 옵션이 "force-cache"로 되어있으면 해당 컴포넌트는 SSG가 적용된다.
이 옵션은 Next.js에서 fetch를 사용할 때만 활성화된다.
(Header 컴포넌트에는 내가 만든 라이브러리가 사용되었다. 내부 로직은 동일.)
개념
SSG는 Static Site Generation 즉, 정적 사이트 생성이다.
말 그대로 정적인 페이지를 생성하는 것으로,
빌드 타임에 컴포넌트가 실행되어 데이터 페칭을 완료하여 데이터가 UI에 적용된 상태로 빌드에 저장된다.
클라이언트에서 요청할 때 별도의 데이터 페칭이 필요없기 때문에 페이지 서빙 속도가 빠른 장점이 있다.
단, 빌드 시에 데이터를 페칭하므로, 빌드 후 실행 중에 api에서 제공하는 데이터가 바뀌더라도
페이지에서는 다시 빌드를 하지 않는 한 만료된 데이터를 제공하게 된다.
사용처
docs, 블로그, 이용 가이드 처럼 모든 유저에게 동일한 데이터가 제공되고, 빌드 타임에 결정 가능한 데이터를 페이지 내부에서 제공할 때에 SSG를 사용하여 페이지 서빙 속도를 올려 UX를 향상시킬 수 있다.
Incremental Static Regeneration
“”전체 사이트를 재빌드할 필요 없이 페이지별로 정적 생성을 사용할 수 있습니다.
마찬가지로 Next.js 공식 문서에 적혀있는 문구이다.
예제
const Post = async () => {
const { data } = await apiClient
.get<Post[]>("/posts")
.withISR(600);
if (!data || data.length === 0) {
return (
<div className="rounded-xl border border-foreground/15 p-6"><p className="text-sm text-foreground/70">No posts found.</p></div>
);
}
return (
<section className="space-y-3">
...</section>
);
};위와 같이 컴포넌트 내에 데이터 페칭 로직이 있을 때,
const data = await fetch('https://api.example.com/a', {
next: { revalidate: 600 }, // 10분마다 재생성
})next.revalidate 옵션이 숫자로 설정되어 있으면 설정한 시간 만큼 간격을 두고 컴포넌트를 재생성한다.
이 옵션또한 Next.js에서 fetch를 사용할 때만 활성화된다.
(Post 컴포넌트에도 내가 만든 라이브러리가 사용되었다. 내부 로직 또한 동일.)
개념
ISR은 Incremental Static Regeneration 즉, 점진적 정적 재생성이다.
재생성 인터벌 만큼 데이터를 캐싱하고 시간이 지나면 자동으로 재생성해주는 장점이 있다.
SSG와 SSR의 중간쯤 되는 느낌.
SSG 처럼 빌드 타임에 데이터를 페칭하여 초기 캐시를 저장한다.
그 후 지정된 시간이 지나고 사용자가 페이지를 요청하면, 백그라운드에서 컴포넌트 재생성을 트리거한다.
사용자에게는 새로이 페칭된 데이터가 담긴 페이지가 서빙되고, 지정된 시간 만큼 데이터를 캐싱한다.
사용처
SSG와 비슷하게 모든 사용자에게 동일한 데이터를 제공하지만, SSG 보다는 업데이트가 활발한 곳에 사용한다.
SSG, ISR과 SSR의 관계
SSR은 익히 알고 있듯, 매 요청마다 api나 서버 자원을 요청한다.
SSG와 ISR은 SSR에 비해 서버 자원을 덜 차지한다는 차이가 있다. 아무래도 당연함
SSR은 쿠키 등 요청한 클라이언트에 따라 달라지는 값들을 처리할 수 있지만,
SSG와 ISR은 쿠키 등등을 사용하지 못한다.
실제로 SSG/ISR 옵션을 활성화한 fetch와 next/headers의 cookies를 함께 사용하면
SSR이 강제되는 것을 알 수 있다. (빌드할 때 DynamicServerError 발생)
추가로, SSG/ISR과 SSR이 적용된 컴포넌트가 같은 렌더링 트리에 있다면, SSG/ISR이 비활성화 된다.
그 이유는 SSR이 Suspense 없이 페이지에 마운트되면 해당 페이지의 경로는 Dynamic Route가 되어 캐싱이 동작하지 않기 때문이다.
<main className="mx-auto w-full max-w-5xl px-5 py-10"><Profile /> // SSR<Post /> // ISR -> 비활성화 됨</main>이를 해결하기 위해
<main className="mx-auto w-full max-w-5xl px-5 py-10"><Suspense fallback={<ProfileSkeleton />}><Profile /></Suspense><Post /></main>Suspense를 사용하여 렌더링 트리를 분리해주면 된다.
위와 같이 코드를 작성하여 실행하면,

ISR로 캐싱된 데이터가 있는 부분과 Suspense의 fallback이 함께 초기 html로 브라우저에 도착하고,
RSC의 스트리밍을 통해 렌더링이 완료된 SSR 컴포넌트가 페이지에 병합된다.
이걸 할려고 SSG와 ISR, SSR을 공부했다.
........
아니 너무 편한거 아님??????????????.....
내가 이때까지 isLoading 상태와 조건부 렌더링을 위해 썼던 코드들은 뭐였는지....
정말이지 세상에 내가 모르는게 너무 많다고 느꼈던 것 같다.
후기
허탈감과 지식을 함께 얻은 것 같다. 2026년 첫날부터 이래도 되는건지 참...ㅋㅋㅋㅋ
그래도 더 빠르게 UX를 챙기며 개발할 수 있는 기술을 배워 뿌듯한 건 부정할 수 없다.
앞으로의 나의 성장과정에서 이 글이 한없이 사소한 일이었으면 좋겠다.
더 방대하고 고급스러운 기술을 배워나가야 할 동기를 얻은 것 같다.
예제 프로젝트 깃허브: https://github.com/cher1shRXD/nextjs-fetching-prac
새해 복 많이 받으세요 여러분~🙇
훈수는 언제나 환영