SSG (Static Site Generation)
1. SSR의 문제점
- 요청 시마다 서버에서 렌더링을 수행해야 하므로 서버 부하 증가
- 트래픽이 많을 경우 응답 속도가 느려질 수 있음
- 캐싱이 어렵고 동적인 요청마다 HTML을 다시 생성해야 함
2. SSG (Static Site Generation)이란?
- SSR의 단점을 해결하는 사전 렌더링 방식
- 빌드 타임에 페이지를 미리 렌더링하여 정적인 HTML을 생성함
- 사전 렌더링이 완료된 HTML은 클라이언트 요청 시 즉시 제공됨
✅ SSG의 장점
- 페이지가 미리 생성되어 있어 사용자 요청에 빠르게 응답 가능
- 트래픽이 많아도 서버 부하가 적음
- SEO에 유리함
❌ SSG의 단점
- 모든 페이지를 미리 빌드해야 하므로 빌드 시간이 길어질 수 있음
- 동적인 데이터를 반영하기 어려움 (매번 빌드를 다시 수행해야 하므로 최신 데이터 반영이 어렵다.)
3. SSG 동작 방식
SSG는 빌드 시점에 정적인 HTML을 생성하여 배포하는 방식으로 동작함. Next.js에서 getStaticProps와 getStaticPaths를 활용하여 구현됨.
- 빌드 타임
- getStaticProps를 실행하여 데이터를 가져옴
- 가져온 데이터를 기반으로 HTML을 생성함
- getStaticPaths를 사용하면 동적 경로도 미리 생성 가능
- 배포 후
- 사용자가 페이지 요청 시, 이미 생성된 정적 HTML을 즉시 제공함
- 서버 부하 없이 빠르게 응답 가능
- 추가적인 데이터 갱신 (Incremental Static Regeneration, ISR 사용 시)
- 지정된 주기마다 페이지를 재생성하여 최신 데이터를 반영 가능
📌 SSG 동작 방식 요약
- next build 실행
- getStaticProps/getStaticPaths가 실행되어 정적 HTML 생성
- .next/server/pages 폴더에 미리 생성된 HTML 파일 저장
- 사용자가 요청하면 즉시 정적 HTML 반환
4. Next.js에서 SSG 사용하기
Next.js에서는 getStaticProps와 getStaticPaths를 활용하여 SSG를 구현할 수 있음.
예제: SSG를 이용한 도서 목록 페이지
📌 getStaticProps 함수 활용
// index.tsx
import SearchableLayout from "@/components/searchable-layout";
import style from "./index.module.css";
import { ReactNode } from "react";
import BookItem from "@/components/book-item";
import { InferGetStaticPropsType } from "next";
import fetchBooks from "@/lib/fetch-books";
import fetchRandomBooks from "@/lib/fetch-random-books";
// 넥스트에서 약속된 이름의 함수로 만들어서 내보내면 해당 페이지는 SSG로 동작하도록 자동으로 설정된다.
export const getStaticProps = async () => {
console.log("인덱스 페이지 호출");
const [allBooks, recoBooks] = await Promise.all([
fetchBooks(),
fetchRandomBooks(),
]);
return {
props: {
allBooks,
recoBooks,
},
};
};
// type도 SSG와 맞도록 InferGetStaticPropsType, getStaticProps로 변경
export default function Home({
allBooks,
recoBooks,
}: InferGetStaticPropsType<typeof getStaticProps>) {
return (
<div className={style.container}>
<section>
<h3>지금 추천하는 도서</h3>
{recoBooks.map((book) => (
<BookItem key={book.id} {...book} />
))}
</section>
<section>
<h3>등록된 모든 도서</h3>
{allBooks.map((book) => (
<BookItem key={book.id} {...book} />
))}
</section>
</div>
);
}
📌 SSG에서 검색 기능 처리
- SSG 과정에서는 쿼리스트링을 처리할 수 없음
- 빌드 타임 이후에 클라이언트 사이드에서 데이터를 요청해야 함
// search/index.tsx
import SearchableLayout from "@/components/searchable-layout";
import { ReactNode, useEffect, useState } from "react";
import BookItem from "@/components/book-item";
import { useRouter } from "next/router";
import { BookData } from "@/types";
import fetchBooks from "@/lib/fetch-books";
export default function Page() {
const [books, setBooks] = useState<BookData[]>([]);
const router = useRouter();
const q = router.query.q;
const fetchSearchResult = async () => {
const data = await fetchBooks(q as string);
setBooks(data);
};
useEffect(() => {
if (q) {
fetchSearchResult();
}
}, [q]);
return (
<div>
{books.map((book) => (
<BookItem key={book.id} {...book} />
))}
</div>
);
}
📌 동적 경로에 SSG 적용하기
- getStaticPaths를 사용하여 빌드 타임에 가능한 모든 경로를 정의해야 함
- fallback 옵션을 통해 동적 데이터 처리 가능
// book/[id].tsx
import { GetStaticPropsContext, InferGetStaticPropsType } from "next";
import fetchOneBook from "@/lib/fetch-one-books";
import { useRouter } from "next/router";
export const getStaticPaths = () => {
return {
paths: [
{ params: { id: "1" } }, // url 파라미터의 값은 반드시 문자열로 설정
{ params: { id: "2" } },
{ params: { id: "3" } },
],
fallback: true, // 존재하지 않는 페이지는 SSR 방식으로 처리
};
};
export const getStaticProps = async (context: GetStaticPropsContext) => {
const id = context.params!.id;
const book = await fetchOneBook(Number(id));
if (!book) {
return {
notFound: true, // 없는 책 번호일 경우 404 페이지 안내
};
}
return {
props: { book },
};
};
export default function Page({
book,
}: InferGetStaticPropsType<typeof getStaticProps>) {
const router = useRouter();
if (router.isFallback) return "로딩중입니다."; // 로딩중일때는 router에서 isFallback 트루이면 로딩중 안내
if (!book) return "문제가 발생했습니다. 다시 시도해주세요"; // 문제 발생시 오류 안내
const { id, title, subTitle, description, author, publisher, coverImgUrl } =
book;
return (
<div className={style.container}>
<div
className={style.cover_img_container}
style={{ backgroundImage: `url('${coverImgUrl}')` }}
>
<img src={coverImgUrl} />
</div>
<div className={style.title}>{title}</div>
<div className={style.subTitle}>{subTitle}</div>
<div className={style.author}>
{author} | {publisher}
</div>
<div className={style.description}>{description}</div>
</div>
);
}
fallback 옵션 1. false
- 존재하지 않는 페이지 404 notfound 반환
fallback 옵션 2. blocking
- SSR 방식처럼 페이지 사전 렌더링
fallback 옵션 3. true
props가 없는 페이지 (getStaticProps로부터 받은 데이터가 없는 페이지) 먼저 반환
- 그다음 SSR 방식으로 Props만 따로 계산 후 데이터 반환
5. 빌드 과정 및 결과
- npm run build 실행 시 .next/server/pages 폴더 안에 정적 HTML 및 JS 파일이 생성됨
- SSG 적용 페이지는 미리 렌더링된 정적 HTML로 제공됨
- SSR 적용 페이지는 요청 시마다 서버에서 렌더링하여 응답
📌 빌드 결과 로그 예시
📌 라우트별 렌더링 방식 표시
o (Static) - 사전 렌더링된 정적 콘텐츠
● (SSG) - getStaticProps 사용하여 사전 렌더링된 정적 HTML
ƒ (Dynamic) - 요청 시 서버에서 렌더링됨 (SSR)
6. 배운점
- SSR은 요청 시마다 서버에서 렌더링하여 최신 데이터를 반영 가능
- SSG는 빌드 타임에 페이지를 미리 생성하여 빠른 응답 속도 제공
- getStaticProps와 getStaticPaths를 사용하여 정적인 페이지 생성 가능
- 동적 페이지의 경우 fallback 옵션을 활용하여 유연한 처리 가능
- CSR, SSR, SSG를 상황에 맞게 혼합하여 사용하는 것이 중요함
'Frontend > Next.js' 카테고리의 다른 글
[Next.js/Page Router] 4. SEO 설정 및 Next.js 프로젝트 배포하기 (0) | 2025.02.05 |
---|---|
[Next.js/Page Router] 3. ISR (Incremental Static Regeneration) (0) | 2025.02.04 |
[Next.js/Page Router] 2. SSR (Server Side Rendering) (0) | 2025.02.02 |
[Next.js] 1. Next.js 프로젝트와 Page Router 기본 (1) | 2025.02.01 |
[Next.js] 0. Next.js 개요 (1) | 2025.01.21 |