인프런 "한 입 크기로 잘라먹는 Next.js" 수강
정적 사이트 생성(SSG)은 SSR의 단점을 보완하는 사전 렌더링 방식이다.
즉, 빌드 타임에 페이지를 미리 생성해두는 것!!
서버가 실행되기 전에 모든 페이지를 미리 만들어두는 방식이라, 사용자가 접속하면 서버는 그냥 미리 준비된 HTML을 "빠르게" 전달해준다.
이 방식은 빌드 시간이 오래 걸려도 개발자만 겪는 고통이라, 사용자 입장에서는 항상 빠르게 응답받을 수 있다.
데이터 최신화가 어렵다.
이전 게시글에서 다룬 getServerSideProps와 동일한 방식으로, SSG 전용 함수인 getStaticProps와 getStaticPaths를 사용하면 된다.
getStaticProps빌드 타임에 필요한 데이터를 미리 불러와 페이지에
props로 전달해준다.
//...
import { InferGetServerSidePropsType } from 'next';
import { ReactNode } from 'react';
export const getStaticProps = async () => {
const [allBooks, randomBooks] = await Promise.all([fetchBooks(), fetchRandomBooks()]);
return {
props: {
allBooks,
randomBooks,
},
};
};
export default function Home({
allBooks,
randomBooks,
}: InferGetStaticPropsType<typeof getStaticProps>) {
//...
여기에서 InferGetStaticPropsType의 역할은 InferGetServerSidePropsType와 동일하게 자동으로 타입을 추론해서 설정해준다.
export const getStaticProps = async () => {
console.log('인덱스 페이지');
const [allBooks, randomBooks] = await Promise.all([fetchBooks(), fetchRandomBooks()]);
return {
props: {
allBooks,
randomBooks,
},
};
};
해당 페이지를 새로고침 하면, 새로고침 할때마다 콘솔에 작성한 내용이 출력된다.
😎: 어라 한 번만 실행되야 하는거 아닌가요?
✍️: 개발 모드로 실행할 때는 수정 결과가 바로 반영되기 때문에, 렌더링 방식이 달라도 요청이 달라도 계속 사전 렌더링을 진행합니당! 그러니 Production(npm run build)로 실행해야 해용!
SSG 설정을 적용한 뒤
npm run build를 실행하여 콘솔 확인해보기

Generating static pages 👉 getStaticProps를 사용한 페이지들이 실제 정적 파일로 생성되는 걸 확인할 수 있음.getStaticProps)가 빌드 타임에 실행된 게 맞다는 증거!● ○ f 심볼의 의미
빌드 로그 맨 아래를 보면 페이지마다 ●, ○, f 표시가 붙어 있는데, 이건 해당 페이지가 어떤 방식으로 사전 렌더링되었는지 알려주는 표식이다.
● (Static)
- 그냥 정적 페이지.
getStaticProps없이도 기본적으로 제공되는 정적 페이지.○ (SSG)
- getStaticProps를 사용해 SSG로 생성된 페이지.
- 빌드 타임에 데이터 패칭까지 끝내고 정적으로 만들어진 페이지.
ƒ (Dynamic)
- 동적으로 서버 사이드 렌더링(SSR)되는 페이지.
-주문형(On demand)으로, 브라우저 요청이 들어올 때마다 페이지가 새로 사전 렌더링 됨.
API Routes의 기본 동작
여기에서
API Routes는 자동으로 SSG 동작한다. 즉, API 라우트에 대해서는 매 요청마다 서버에서 새롭게 실행된다는 점!
Production 모드에서 확인하기 🚀
npm run build후npm run start로 production 모드를 실행하면, SSG, SSR로 설정한 기본 정적 페이지들이 각각 어떤 방식으로 동작하는지 브라우저에서 직접 확인할 수 있다.
Query String(?q=검색어)이라는 건 브라우저가 요청을 보낼 때 URL에 붙여서 전달하는 값getStaticProps는 빌드 타임에 실행된다. 빌드 타임에는 브라우저 요청이 아예 없으니query를 알 수가 없어서, GetStaticPathsContext 타입에 query가 아예 정의되어 있지 않음
이를 해결하기 위해서는, 빌드 타임에서
query값을 알아야 하는데 이건 불가능하다.
😎: 정적인 페이지를 생성할 때 query string을 활용할 수 없으면 어떻게 동작시켜요?
✍️: 사전 렌더링이 끝난 뒤, 클라이언트 측에서 직접 query를 읽고 처리해야 한다. (아래의 방식처럼 해결하면 된다!)
//...
export default function Page() {
const [books, setBooks] = useState<BookData[]>([]);
const router = useRouter();
const q = router.query.q as string;
const fetchSearchResult = async () => {
const data = await fetchBooks(q);
setBooks(data);
};
useEffect(() => {
if (q) {
fetchSearchResult();
}
}, [q]);
//...
useRouter 훅을 이용해 router.query에서 값을 꺼내와서 데이터 관련 부분을 제외하고 나머지 부분만 렌더링해서 브라우저에게 보내주는 걸 확인할 수 있으며,

사진 속 결과처럼 useEffect를 통해서 서버에게 데이터를 직접 요청하는 방식인 SSR으로 동작하는 걸 확인할 수 있다.
getStaticPaths동적 라우팅 페이지(
/posts/[id]같은)에서 어떤 경로를 미리 빌드할지 정해줄 수 있다.
//...
import type { GetStaticPathsContext, InferGetStaticPropsType } from 'next';
import { ReactNode } from 'react';
export const getStaticProps = async (context: GetStaticPathsContext) => {
const q = context.query.q as string;
const books = await fetchBooks(q);
return {
props: {
books,
},
};
};
export default function Page({ books }: InferGetStaticPropsType<typeof getStaticProps>) {
이때 같은 방식으로 적용하고 실행하면 아래와 같은 오류가 발생한다.
Error: getStaticPaths is required for dynamic SSG pages
and is missing for '/book/[id]'.
이는 SSG 방식이 "어떤 경로들을 빌드 타임에 생성할지"를 미리 알아야 하는데, getSTataicPaths가 없으니 Next.js가 어떤 페이지를 만들어야 하는지 몰라서 발생한 오류이다.
export const getStaticPaths = () => {
return {
paths: [
{ params: { id: '1' } },
{ params: { id: '2' } },
{ params: { id: '3' } },
],
fallback: false,
};
};

paths: 빌드 타임에 미리 렌더링할 경로들을 배열로 작성(여기서는 /book/1 ~ /book/3)params: URL 파라미터를 객체 형태로 지정 (⚠️파라미터 값은 꼭 문자열로!)fallback: 설정하지 않은 경로로 접근했을 때 어떻게 처리할지 결정
👉 이렇게 작성하면 /book/1 ~ /book/6은 빌드 타임에 정적 페이지로 만들어지고, 그 외 경로는 404가 뜨게 된다.
fallback 옵션위에서 사용한 fallback 옵션을 false 이외에도 유연하게 설정할 수 있다.
path만 접근 가능

⚠️ 주의할 점은
사전 렌더링 하는 시간이 길어지는 경우 브라우저에서 오랜 시간 기다려야 하는 상황이 발생할 수 있다. 이럴 때는true로 설정하면 된다.


🔥 예외처리을 더 개선해자!
//... export const getStaticProps = async (context: GetStaticPropsContext) => { const id = context.params!.id; const book = await fetchOneBook(Number(id)); if (!book) { return { notFound: true, }; } return { props: { book, }, }; }; export default function Page({ book }: InferGetStaticPropsType<typeof getStaticProps>) { const router = useRouter(); if (router.isFallback) return '로딩 중입니다.'; const { title, subTitle, description, author, publisher, coverImgUrl } = book; return ( //...
useRouter의 속성isFallback으로fallback상태를 확인하여,로딩 중과404를 구분해 사용자에게 보여줄 수 있다.
404인 경우에Not Found로 이동할 수 있게noFound속성을true로 설정해준다.
(존재하지 않은 페이지에 접근하면 이래의 이미지의 결과를 확인할 수 있다.)