이전에 동적라우팅에 대해서 리퀘스트가 일어날 때 마다 생성하는 Page를 테스트 해봤었는데 빌드타임에 Static Page를 만들 수 있는 방식도 존재했다.
api path 경로에 따라 데이터는 달라지지만 내용이 자주 바뀌지 않는 페이지에 유용할 것 같다. /post/2
getStaticPaths
와 getStaticProps
를 활용하면 되는데 build시에 외부 데이터로 받아올 동적 path의 목록들을 getStaticPaths를 활용해서 받아오고 getStaticProps에게 전달하여 Static Page를 만드는 방식이다.
코드는 다음과 같다.
// pages/post/[id].tsx
export async function getStaticPaths() {
const res = await fetch(`${JSONPLACEHOLDER_URL}/posts`);
const allPostsData = await res.json();
const paths = allPostsData.map((post: PostProps) => ({
params: {
id: post.id.toString(),
},
}));
return {
paths,
fallback: false,
};
}
export async function getStaticProps({ params }: any) {
const res = await fetch(`${JSONPLACEHOLDER_URL}/posts/${params.id}`);
const postData = await res.json();
return {
props: {
postData,
},
};
}
처음에는 아래와 같이 paths를 넘겨주었는데 id는 string 형식으로 제공해야하는 것 같다.
const paths = allPostsData.map((post: PostProps) => ({
params: {
id: post.id,
},
}));
(여기서 id는 동적 라우팅하는 filename에 따라 다르며 나의 경우에는 pages/post/[id].tsx
이여서 id로 작성해주었다.)
build시에 데이터를 가져와 Static Page를 미리 생성하는것을 볼 수 있었다.
(여기서 100개의 page가 생긴이유는 getStaticPaths에서 100개의 id를 가져왔기 때문이다.)
위의 코드를 보면 getStaticPaths에서 fallback을 볼 수 있으며 역할은 다음과 같다.
우선 express로 간단하게 아래와 같이 데이터를 반환하는 api를 만들어두었다.
const data = [
{
"userId": 1,
"id": 1,
"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
"body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
},
{
"userId": 1,
"id": 2,
"title": "qui est esse",
"body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
},
{
"userId": 1,
"id": 3,
"title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
"body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"
}
];
app.get('/posts', (req, res) => {
return res.json(data);
});
app.get('/posts/:id', function(req, res, next) {
const { id } = req.params;
let post = data.filter(u => u.id == id )[0];
if(!post) post = {};
return res.json(post);
});
app.listen(port, function () {
console.log(`🔥Server on! http://localhost:${port}`);
});
이제 build시에 3개의 Static Page가 만들어지게 되고 url을 변경해보며 실행해보면 3개의 포스트를 잘 불러오는 것을 볼 수 있다.
여기서 만약 백엔드에서 데이터 변경이 있어 4번째 포스트가 생긴 상태이지만 만들어두지 않았던 4번째 포스트로 url로 이동
하게 되면 어떻게 될까?
이때의 역할을 해주는 것이 fallback이다.
fallback이 false일 때 결과는 다음과 같이 404 페이지를 보여주게 된다.
이제 fallback을 true로 변경해보았다.
그리고 빌드를 하려니까 에러가 발생한다.
잘 읽어보면 typeError로 아래의 코드에서 undefined로 넘어오게 되어서 오류가 발생하는것을 알 수 있는데
const { userId, title, body } = postData;
해당 문제를 해결하기위해 render하는 부분에서 isCallback값을 체크했다.
function PostDetail({ postData }: Props) {
const { isFallback } = useRouter();
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (isFallback) {
return <div>Loading...</div>;
}
const { userId, title, body } = postData;
return (
<ul>
<Post userId={userId} title={title} body={body} />
</ul>
);
이제는 잘 build 되었고 처음에는 3개가 생성되었지만 여기서 4번째 포스트로 url이동을 하게 되면 그 때 새롭게 생긴 데이터를 fetch 후 page를 만들면서 페이지를 보여주는 것을 확인할 수 있었다.
http://localhost:3000/post/4 을 친 시점에 Static page가 만들어졌다.
그리고 처음에 페이지가 먼저 렌더되어 (isCallback: true) Loading...이 보여진 후 fetch가 끝난 시점에 (isCallback: false)로 변하면서 새로운 데이터를 보여주었다. 그래서 위에서 빌드했을 때 오류가 발생하는듯 하다.
여기서 만약 isFallback을 사용하여 새로운 동적 라우팅에 대해 Static Page를 만드는 동안 Loading...이라는 페이지를 보여주고 싶지 않다면 fallback: 'blocking'
옵션을 사용하면 된다. (SSR처럼 동작한다.)