SSG·generateStaticParams·정적 export
SSG·generateStaticParams·정적 export
요약: 빌드할 때 HTML을 미리 만들고, 동적 주소는 generateStaticParams로 목록을 넘기며, 정적 내보내기 제약을 확인한다.
1. 왜 이 주제가 필요한가
검색엔진과 카카오·슬랙 미리보기는 첫 응답 HTML을 본다. 빌드 시점에 제목과 본문이 들어 있으면 공유·SEO가 안정적이다. CDN(가까운 서버에서 파일을 나눠 주는 망)에도 올리기 쉽다.
SSG(Static Site Generation, 빌드할 때 HTML을 고정하는 방식) 가 그 수단이다. /posts/hello처럼 칸이 바뀌는 주소도, 미리 어떤 slug가 있는지 알려 주면 같은 방식으로 만들 수 있다.
2. 한 줄 규칙
공개되는 모든 slug를 generateStaticParams에 넣는다. 페이지에서 await params를 써도 정적 HTML이다.
정적 내보내기(output: 'export')는 서버가 없다. 빌드 때 읽을 수 있는 데이터만 쓴다.
3. 예제
generateStaticParams
// lib/posts.ts
export type Post = {
slug: string;
title: string;
body: string;
};
const POSTS: Post[] = [
{slug: 'hello', title: '첫 글', body: '본문 A'},
{slug: 'world', title: '둘째 글', body: '본문 B'},
];
export function getAllPosts(): Post[] {
return POSTS;
}
export function getPost(slug: string): Post | undefined {
return POSTS.find((p) => p.slug === slug);
}
// app/posts/[slug]/page.tsx
import {notFound} from 'next/navigation';
import {getAllPosts, getPost} from '@/lib/posts';
type Props = {
params: Promise<{slug: string}>;
};
export function generateStaticParams() {
return getAllPosts().map((post) => ({slug: post.slug}));
}
export default async function PostPage({params}: Props) {
const {slug} = await params;
const post = getPost(slug);
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<p>{post.body}</p>
</article>
);
}
npm run build 로그에서 /posts/hello가 ●(정적)으로 찍히는지 확인한다. ƒ는 서버가 필요한 동적 라우트다.
await params는 Next.js 15 API다. 페이지를 CSR(브라우저에서 JS로 그리는 방식)로 바꾸지 않는다. generateStaticParams가 있으면 해당 slug는 미리 렌더된 HTML이 나온다.
빌드에 없는 slug
export const dynamicParams = false;
dynamicParams가 false면 generateStaticParams에 없는 주소는 404다. 문서·블로그는 보통 알려진 slug만 빌드한다.
기본값 true면 빌드에 없던 slug를 서버가 그때 만들 수 있다. 정적 내보내기에서는 서버가 없으므로 이 경로에 기대지 않는다.
정적 내보내기
// next.config.ts
import type {NextConfig} from 'next';
const nextConfig: NextConfig = {
output: 'export',
trailingSlash: true,
images: {
unoptimized: true,
},
};
export default nextConfig;
결과는 out/ 아래 HTML·JS·CSS다. GitHub Pages, S3, 일반 웹 서버에 올린다.
되는 것:
- 빌드 시 마크다운·공개 API를 읽어 HTML 생성
generateStaticParams로 만든 상세 페이지- 클라이언트에서 외부 API를 치는 화면(SEO 본문이 필요 없을 때)
안 되는 것(대표):
- 요청마다 도는 Route Handler
- 미들웨어로 세션을 검사하는 보호
- ISR·
revalidateTag같은 런타임 재검증 - 서버만 가능한 이미지 최적화(그래서
unoptimized)
호스팅에 서버가 있으면 output: 'export'를 켜지 않는 편이 기능이 넓다. 정적만 필요하면 켜서 단순화한다.
notFound
import {notFound} from 'next/navigation';
const post = getPost(slug);
if (!post) notFound();
notFound()는 not-found.tsx를 렌더한다. throw new Error와 다르다. 없는 글은 404, 파일 읽기 실패는 error.tsx로 나눈다.
목록·sitemap·generateStaticParams에 같은 slug 집합을 쓴다. 한쪽만 빠지면 죽은 링크가 생긴다.
4. 흔한 실수
| 실수 | 결과 | 대안 |
|---|---|---|
generateStaticParams를 안 넣는다 |
동적 라우트가 되거나 export 빌드 실패 | 공개 slug를 모두 반환 |
await params면 CSR이라고 생각한다 |
불필요하게 'use client' |
서버 페이지 + 정적 params |
export인데 revalidate / Route Handler |
빌드 에러 또는 죽은 엔드포인트 | 빌드 타임 데이터만 |
| slug는 빌드했는데 sitemap은 빠짐 | 검색이 일부 URL만 봄 | 같은 목록 함수를 공유 |
dynamicParams: true에 export로 의존 |
새 글 URL이 404 | 글 추가 후 다시 빌드 |
정리
SSG는 “주소 목록을 빌드에 알려 주고 HTML을 미리 굽는 것”이다. 동적 칸은 generateStaticParams가 그 목록이다.
정적 내보내기는 서버 기능을 포기하는 선택이다. 공개 문서에 맞고, 로그인·웹훅에는 보통 맞지 않는다.
연습
[slug]세 개를 SSG로 빌드한 뒤out/posts/hello/index.html에 제목이 있는지 연다.- 페이지를
await params로 바꿔도 빌드 로그가 정적(●)인지 확인한다. output: 'export'를 켠 뒤 깨지는 API·미들웨어·이미지를 목록으로 적는다.
댓글
0아직 댓글이 없습니다. 첫 댓글을 남겨보세요.