지난 게시물 서버 사이드 렌더링(SSR) 방식에 정적 사이트 생성(SSG) 방식에 대해 알아보고자 한다.
정적 사이트 생성(SSG)
SSG 방식은 빌드 타임에 미리 페이지를 사전 렌더링 해두는 방식이다.
위의 그림과 같이 npm run build 명령어로 빌드할 때, 사전 렌더링을 진행해서 페이지를 미리 딱 한번만 생성하고 더이상 페이지를 생성하지 않는다.
그리고 빌드가 완료된 이후에 실제 넥스트 앱을 가동하면 브라우저가 접속 요청을 보내면 넥스트 서버는 빌드 타임에 미리 만들어둔 페이지를 바로 제공하게 된다. 그리고 그럼으로써 사용자는 매우 빠른 시간 안에 완성된 화면을 볼 수 있게 된다.
정리하면 이러한 과정으로 인해 SSG 사전 렌더링 방식은 사전 렌더링에 많은 시간이 소요되는 페이지더라도 사용자의 요청에는 매우 빠른 속도로 응답이 가능하게 된다.
그러나 빌드 타임에 사전 렌더링을 한번만 진행하기 때문에 매번 똑같은 페이지만 응답하게 되어 최신 데이터 반영은 어려워 데이터의 변화가 없는 마케팅 페이지, 블로그 게시물과 같이 정적으로 생성된 정보를 요청마다 동일한 정보로 반환하는 경우에 사용하는게 좋다.
SSG 정적 경로 설정하기
1. Index 페이지 SSG 적용하기
먼저 index(Home) 페이지를 SSG를 적용해본다.
export const getServerSideProps = async () => {
const [allBooks, recoBooks] = await Promise.all([
fetchBooks(),
fetchRandomBooks(),
])
return {
props: {
allBooks,
recoBooks,
},
};
};
export default function Home({
allBooks,
recoBooks
}: InferGetServerSidePropsType<typeof getServerSideProps>) {
return (
(...)
)
}
기존에 SSR 을 위해 getServerSideProps 와 InferGetServerSidePropsType<typeof getServerSideProps> 되어 있던 코드를 SSG 로 변경하기 위해 getStaticProps 와 InferGetStaticPropsType<typeof getStaticProps>로 변경한다
export const getStaticProps = async () => {
const [allMovies, recoMovies] = await Promise.all([
fetchMovies(),
fetchRandomMovies(),
]);
return {
props: {
allMovies,
recoMovies,
}
}
}
export default function Home({
allMovies,
recoMovies,
}: InferGetStaticPropsType<typeof getStaticProps>) {
return (
(...)
);
}
getServerSideProps 와 마찬가지로 getStaticProps 함수를 사용하면 SSG 방식으로 동작하게 된다.
getStaticProps 를 사용하게 되면 props 타입을 InferGetStaticPropsType<typeof getStaticProps> 로 추가적으로 변경하면 된다.
그리고 나서 npm run build 명령어를 입력하면 아래와 같은 빌드 결과가 나온다.
2. Search 페이지 SSG 적용하기
이어서 이번에는 서치페이지에서 SSG를 적용해본다.
export const getStaticProps = async (context : GetStaticPropsContext) => {
const q = context.query.q
const books = await fetchBooks(q as string)
return {
props: {
books,
},
}
}
인덱스 페이지에서 처럼 getStaticProps를 적용하고 context 도 GetStaticPropsContext 로 변경하면 될 것 같지만
그러면 GetStaticPropsContext 에는 query 라는 프로퍼티가 없다는 에러가 발생하된다.
이는 사실 당연한 에러이다, 이유는 검색 같이 동적인 값은 빌드 시에 알 수 없는 값이기 때문에 GetStaticPropsContext 에는 query 라는 프로퍼티는 존재할 수 없다.
그럼 이런한 제약사항에서 서치 페이지를 SSG 방식으로 동작하게 하고 싶을 경우
컴퍼넌트의 클라이언트 측에서 직접, 리액트 앱에서 했었던 방식으로 데이터 패칭해야한다.
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 className={style.container}>
{books.map((book) => (
<BookItem key={book.id} {...book} />
))}
</div>
)
}
getStaticProps 은 지우고 CSR 방식으로 코드를 작성하게 되면 이 서치페이지는 getStaticProps와 getServerSideProps 가 모두 사라졌기 때문에 기본적으로는 SSG 방식으로 동작하게된다.
그리고 쿼리 스트링으로 전달되는 검색어는 빌드 타임알 수 없어 사전 렌더링 과정에서는 결국 이 페이지의 레이아웃만 렌더링하게 되고 이 컴포넌트가 마운트된 이후에 클라이언트 사이드 측에서 이 컴포넌트가 다시 실행되면서 직접 쿼리 스트링으로 검색어에 해당하는 데이터를 불러와서 렌더링하게 된다.
결국 서치페이지는 레이아웃은 SSG 방식으로 검색결과는 CSR 방식으로 동작하게 하여 SSG를 적용해볼 수 있다.
SSG 동적 경로 설정하기
마지막으로 동적 경로에 해당하는 페이지에 SSG를 적용하기 위해 [id].tsx 로 이동한다.
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 "문제가 발생했습니다. 다시 시도해주세요.";
const {
id,
title,
subTitle,
description,
author,
publisher,
coverImgUrl
} = book
return (
(...)
)
}
우선 인덱스 페이지와 같이 SSR 코드를 SSG 와 관련된 코드로 변경한다.
그러면 동적인 경로를 갖는 페이지는 getStaticPaths 라는 함수가 추가로 필요하다는 에러가 발생하게 된다.
이는 [id].tsx 는 동적인 경로를 갖는 페이지이기 때문에 빌드 타입에서 이에 대한 경로를 미리 설정해야 하기 때문에 발생하는 에러이다.
위의 그림처럼 빌드 타임에 동적인 경로를 가질 수 있는 페이지에 미리 경로를 설정하게 되면 이에 대한 페이지를 사전 렌더링하게 된다.
그리고 경로를 설정할 때 사용되는 함수가 위에서 발생했던 에러에서 안내되었던 getStaticPaths 함수이다.
getStaticPaths를 적용하는 방법은 아래와 같다.
export const getStaticPaths = async () => {
const books = await fetchBooks();
return {
paths: [
{ params: {id : "1"}},
{ params: {id : "2"}},
{ params: {id : "3"}},
]
}
}
이런식으로 설정하게 되면 url 파라미터의 값으로 id 값이 1, 2, 3이 들어올 수 있고, book/1, book/2, book/3 페이지가 존재할 수 있다 라는 의미가 된다.
그러면 1,2,3 이외에 생길 수 있는 동적인 경로에 대한 설정은 어떻게 할 수 있을까라는 의문이 생길 수 있는데, 이런 예외 사항을 위한 옵션인 fallback 옵션을 사용하면 된다.
fallback 옶션의 값으로는 false, bolcking, true 세가지의 값이 존재하는데 이를 정리하면 아래와 같다.
- false : 빌드 시에 생성되지 않은 정적 페이지를 요청하는 경우, 404 페이지를 응답.
- blocking : 빌드 시에 생성되지 않은 정적 페이지를 요청하는 경우, SSR 방식으로 제작한 정적 페이지를 사용자에게 제공.
- true : 빌드 시에 생성되지 않은 정적 페이지를 요청하는 경우, 먼저 fallback 페이지를 제공하고, 서버에서 정적 페이지를 생성하여 생성되면 사용자에게 해당 페이지를 제공한다.
fallback: true 를 적용한 코드는 아래와 같다.
export const getStaticPaths = async () => {
const books = await fetchBooks();
return {
paths: [
{ params: {id : "1"}},
{ params: {id : "2"}},
{ params: {id : "3"}},
],
fallback : true
}
}
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>) {
const router = useRouter();
// fallback 상태일때
if (router.isFallback) return "로딩 중 입니다.";
// 데이터가 없을때
if(!book) return "문제가 발생했습니다. 다시 시도해주세요.";
const {
id,
title,
subTitle,
description,
author,
publisher,
coverImgUrl
} = book
return (
(...)
)
}
이려면 경로로 설정하지 않았던 페이지는 먼저 "로딩 중 입니다." 라는 텍스트를 보여주고 서버에서 Props 를 계산해서 데이터를 전달하게 된다.
Next.js에서 사전 렌더링 오류 발생
Error occurred prerendering page "/lib/fetch-random-movies". Read more: https://nextjs.org/docs/messages/prerender-error
이 오류가 발생한 이유
페이지를 사전 렌더링하는 동안 next build오류가 발생
- pages/디렉토리 에 잘못된 파일 구조 또는 비페이지 파일이 있습니다.
- 사전 렌더링 중에 사용할 수 없는 채워질 소품을 예상합니다.
- 적절한 검사 없이 구성 요소에서 브라우저 전용 API 사용
- getStaticProps또는 에서 잘못된 구성getStaticPaths
이 오류는 내가 폴더 구조를 잘못해서 발생했다, 그래서 폴더 구조를 조정하니 해결이 되었다.
출처 : [한 입 크기로 잘라먹는 Next.js(15+) 강의]
'Next.js' 카테고리의 다른 글
NextJS : SEO 설정하기 (3) | 2024.09.20 |
---|---|
NextJs : ISR 사전 렌더링 (0) | 2024.09.20 |
NextJs : 사전 렌더링과 데이터 페칭(SSR) (1) | 2024.09.17 |
NextJs : 글로벌/페이지 레이아웃 설정하기 (0) | 2024.09.13 |
NextJs : 네비게이팅/프리 페칭/API Routes/스타일링 (0) | 2024.09.12 |