


Next.js에서 제공하는 사전 렌더링 방법 중 2번째 방법.
SSR의 방식에서 발생하는 단점을 (데이터 페칭이 늦어질 경우 뒤에 위치한 모든 과정들이 덩달아 지연) 해결할 수 있다.



export const getStaticProps = async () => {
const [allBooks, recoBooks] = await Promise.all([
fetchBooks(),
fetchRandomBooks(),
]);
return {
props: {
allBooks,
recoBooks,
},
};
};
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의 정상 동작 확인을 위해서는 프로젝트를 실행할 때, 프로덕션 모드로 실행해줘야 한다.
SSR, SSG가 혼합되어 있을 때. 프로젝트를 빌드해보면 Next.js에서 어느 파일이 어떤 방식으로 사전 렌더링을 실행하는지 구분해서 보여준다. 개발 시 참고.
Dynamic? 브라우저에서 요청을 받을 때마다 다이나믹하게 사전 렌더링이 실행된다. SSR 방식. api Route의 경우 Next.js가 기본적으로 SSR 방식으로 지정해준다.
Static? SSG 방식으로 동작하는데, 개발자가 getServerSideProps나 getStaticProps 함수를 설정하지 않을 경우 Next.js에서 기본적으로 SSG 방식으로 적용시켜둔다. 개발자가 명시적으로 설정을 하지 않았으니 구분은 따로 이루어지는 것.
export const getStaticProps = async (
context: GetStaticPropsContext
) => {
const q = context.query.q;
const books = await fetchBooks(q as string);
return {
props: {
books,
},
};
};
당연하지만, SSG 방식을 사용한다면 context 객체를 통해서 쿼리 스트링이나 param 값을 가져올 수가 없다. SSG는 빌드 타임 중에 딱 1번 모든 작업을 수행하고, 빌드 타임 중에는 쿼리 스트링과 같은 값을 가져올 방법이 없기 때문. 검색 결과를 서버에서 불러온다는 등의 작업 등은 아예 수행이 불가능하다. SSG의 단점.
SSG에서 이런 작업을 수행하고 싶다면, React.js에서 사용했던 방법을 쓰면 된다.
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>
);
}

export const getStaticProps = async (
context: GetStaticPropsContext
) => {
const id = context.params!.id;
const book = await fetchOneBook(Number(id));
return {
props: {
book,
},
};
};
export default function Page({
book,
}: InferGetStaticPropsType<typeof getStaticProps>) {
if (!book) return "문제가 발생했습니다 다시 시도하세요";
}


다만 동적 경로 방식에서는 이외에도 getStaticPaths라는 함수를 추가로 구현해주어야 한다. id 페이지를 예시로 들자면, id 값은 변화하는 값이고. 변화하는 값은 SSG 방식에서 사용할 수 없는 형식이기 때문.
따라서 동적 경로 방식에서 SSG를 사용하기 위해서는, 이 페이지에서 어떤 경로들이 존재하는지 모든 경우의 수를 작성해주어야 한다.

export const getStaticPaths = () => {
return {
paths: [
{ params: { id: "1" } },
{ params: { id: "2" } },
{ params: { id: "3" } },
],
fallback: false,
};
};
서버를 통해 데이터베이스에 필요한 값을 모두 받아오던지, 아니면 페이지 컴포넌트 내부에서 필요한 값들을 작성해주면 된다.
getStaticPaths 함수는 이 페이지에서 생성할 수 있는 모든 경로들을 설정해준다. 그리고 getStaticProps는 이를 이용해서 생성 가능한 모든 페이지들을 사전에 렌더링한다.
getStaticPaths 함수 내부의 params의 값들은 반드시 문자열 형식으로 입력해주어야 한다.



blocking 방식의 경우, 한번 생성된 페이지들은 Next 서버에 저장이 된다. 새로고침을 하더라도 새로 생성하거나 하지 않는다는 것. SSR으로 생성해서 SSG로 동작한다 정도로 이해하면 좋다.
빌드 타임에 모든 데이터를 다 불러오기가 부담스럽거나, 새로운 데이터가 추가되어야 하는 상황이라면 blocking 방식을 사용해서 SSR + SSG의 혼합 적용을 염두해볼 수 있다.

그런데, SSR 방식의 사전 렌더링은 추가적인 데이터 요청으로 시간이 지연될 수 있다. 이 경우에는 fallback 옵션을 true로 설정해볼 수 있다.
true에서는 우선 데이터 페칭을 하지 않고 props가 없는 페이지를 사용자에게 반환해준다. 데이터가 없는 레이아웃만 보내기 때문에, 사용자에게 화면을 우선 빠르게 보여주고 데이터는 후속으로 처리한다.
export default function Page({
book,
}: InferGetStaticPropsType<typeof getStaticProps>) {
const router = useRouter();
if (router.isFallback) return "로딩중입니다";
if (!book) return "문제가 발생했습니다 다시 시도하세요";
if (!book) {
return {
notFound: true,
};
}
}
fallback이라는 단어는 페이지 컴포넌트가 서버로부터 아직 데이터를 전달받지 못한 상태를 뜻하기도 한다.
페이지를 정상적으로 렌더링 하기 위해서는 데이터가 모두 받아와져야 한다. 그런데 그 사이에 시간적인 지연이 발생한다면? 사용자에게 데이터를 불러오고 있다는 사실을 명시적으로 보여주어야 한다.
그런데 데이터의 존재 유무만을 가지고 로딩 화면의 렌더링 여부를 결정하기에는, 진짜 문제가 발생해서 데이터가 반환되지 않았을 경우를 구분해서 처리할 수가 없다.
이를 위해서 router 객체의 isFallback 상태를 사용할 수 있다. 데이터의 로딩 중, 그리고 로딩 실패 상황을 구분할 수 있는 것.
로딩 실패 상황을 조금 더 세련되게 처리하고 싶다면, return 키워드와 notFound 옵션을 사용하면 된다. 이 경우 페이지가 404 페이지로 이동되게 된다.
