이번에 Next에서 동적라우팅으로 getStaticPaths가 존재한다는걸 확인하고 사용하다 중요한 설정인 fallback관련을 확실히 구별해서 작성해보려합니다.
Next 에서 기존 동적라우팅 방법말고, getStaticPaths
를 이용하면 빌드타임에 해당 페이지를 생성할 수 있습니다. 해당 페이지 컴포넌트에 getStaticPaths
와 getStaticProps
를 사용하면 가능한데, getStaticPaths에 관해 자세히 살펴보겠습니다.
getStaticProps
에서는 외부 데이터에 따른 페이지에 요청된 데이터를 가져와 빌드타임 HTML생성시기에 해당 data를 넣어주려고 사용했었습니다.
getStaticPaths
는 각 페이지 경로가 외부 데이터에 따라서 생성이 필요할경우 주로 사용을하며, 해당 페이지를 정적으로 생성하게 해줍니다.
코드로써 보여드리면 이런 형식입니다.
import React, { useEffect } from "react"
import axios from 'axios'
import { GetStaticPaths, GetStaticProps,GetServerSideProps } from "next"
import styled from "@emotion/styled"
interface Movie {
background_image : string,
genres: string[],
data_uploaded: string,
rating: number,
id: number,
title: string,
year: number,
summary: string
}
function Detail() {
return(
<div>
디테일이다
</div>
)
}
export const getStaticPaths: GetStaticPaths = async () => {
const {data} = await axios.get(`https://yts.mx/api/v2/list_movies.json?minimum_rating=8.8&sort_by=year`)
function idParamsAll() {
return data.data.movies.map((item:Movie)=>{
return {
params: {
id: item.id.toString()
}
}
})
}
const paths = idParamsAll()
return {
paths,
fallback: false
}
}
export const getStaticProps: GetStaticProps = async({params}) => {
const {data} = await axios.get(`https://yts.mx/api/v2/movie_details.json?movie_id=${params?.id}`)
return {
props: {
data
}
}
}
export default Detail
이 때, 어떤 path들에 대해서 정적 페이지 생성을 할지 정하는 getStaticPaths
함수는 반환 객체로 paths
키와 fallback
키를 반드시 포함시켜야합니다.
// getStaticPaths
return {
paths: [
{ params: { id: '1' } },
{ params: { id: '2' } }
],
fallback: ...
}
pages/users/[id].js에서 getStaticPaths 함수가 아래와 같은 객체를 반환하면,
Next JS는 users/1
, users/2
페이지를 빌드타임에 생성하게 됩니다.
어떤 path의 페이지들을 빌드 타임에 생성할지 정하는 배열이다.
paths 배열에서 각각의 params 값은 페이지 이름에 있는 파라미터와 일치해야한다.
여기서 주의할 점들은 다음과 같다.
여기서 이해하느라 애먹었던 부분이 존재하는데, 그것이 바로 fallback 설정입니다.
빌드 타임에 생성해놓지 않은 path로 요청이 들어온 경우 어떻게 할지 정하는 boolean
또는 'blocking'
값입니다.
getStaticPaths로 반환되지 않은 path외 모든 path는 자동으로 404페이지를 라우팅해줍니다.
이럴때 false를 넣는다.
true가 되면 getStaticProps의 동작이 변한다,
fallback 상태란
"fallback" 상태일 때 보여줄 화면은 next/router의 router.isFallback 값 체크를 통해서 조건 분기하면 된다. 이때 페이지 컴포넌트는 props로 빈값을 받게된다.
예시
// pages/posts/[id].js
import { useRouter } from 'next/router'
function Post({ post }) {
const router = useRouter()
// If the page is not yet generated, this will be displayed
// initially until getStaticProps() finishes running
if (router.isFallback) {
return <div>Loading...</div>
}
// Render post...
}
이럴때 true로 넣는다
이럴경우
몇몇 페이지들만 정적으로 생성하게 하고, fallback 옵션을 true로 설정해주면 이후 요청이 오는 것에 따라서 정적 페이지들을 추가하게 된다전반으로 true
일 경우와 비슷하게 동작하지만, 최초 만들어놓지않은 path에 대한 요청이 들어온 경우 fallback 상태를 보여주지 않고 SSR처럼 동작한다. 이후 true 옵션과 같이 기존의 정적 페이지 리스트에 새로 생성한 페이지를 추가한다.
import axios from 'axios'
import _ from 'lodash';
export default function UserDetailPage({ user }) {
return (
<div>
{user.id} / {user.name} / {user.email}
</div>
)
}
export async function getStaticPaths() {
const { data: users } = await axios.get('https://jsonplaceholder.typicode.com/users');
const paths = _.map(_.slice(users, 0, 5), (user) => {
return { params: {id: _.toString(user.id)}};
});
return {
paths,
fallback: false
}
}
export async function getStaticProps(context) {
const { id } = context.params;
const { data: user } = await axios.get(`https://jsonplaceholder.typicode.com/users/${id}`);
return {
props: {
user
}
}
}
정적으로 생성한 path이외는 404페이지
import axios from 'axios'
import { useRouter } from 'next/router';
import _ from 'lodash';
export default function UserDetailPage({ user }) {
const router = useRouter();
// fallback version
if (router.isFallback) {
return (
<div>
Loading...
</div>
)
}
return (
<div>
{user.id} / {user.name} / {user.email}
</div>
)
}
export async function getStaticPaths() {
const { data: users } = await axios.get('https://jsonplaceholder.typicode.com/users');
const paths = _.map(_.slice(users, 0, 5), (user) => {
return { params: {id: _.toString(user.id)}};
});
return {
paths,
fallback: true
}
}
export async function getStaticProps(context) {
const { id } = context.params;
const [{ data: user }] = await Promise.all([
axios.get(`https://jsonplaceholder.typicode.com/users/${id}`),
timeout(5000)
]);
return {
props: {
user
}
}
}
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
fallback
이 true
인 경우에는 사전에 빌드하지 않은 path
에 대해서 요청이 들어올시 첫 요청 시기에 getStaticProps
호출을 통해 페이지를 만들고, 빌드된 path 리스트
에 추가한다. 페이지가 아직 만들어지지 않았을 때, 페이지의 fallback
버전을 렌더링한다.
위 코드 예시에서는 의도적으로 getStaticProps
의 실행을 늦추기 위해 timeout 함수를 선언해서 사용했다. getStaticProps
의 실행에는 최소 5초가 걸리게된다.
즉, 새로운 path요청은 5초 이상의 fallback 상태이다.
빌드 후에 파일을 확인해보면, [id].html이 추가로 생성되는데, 이 파일이 /users/[id] 라우트로의 fallback 버전이 된다. 실제로 파일을 열어보면 아래와 같이 페이지 컴포넌트에서 정의한 fallback 버전을 볼 수 있다. 참고로, fallback 버전이 지정되지 않으면 빌드과정에서 오류가 발생한다.
사전에 빌드되지 않은 path에 대한 요청이 들어가게 되면,
위와 같이 페이지 생성이 완료되기 전까지 fallback 버전을 보여주다가, 생성이 완료되면 생성된 페이지를 저장하고, 보여주게 된다.
이렇게 새로운 path로의 첫 요청은 오래 걸리지만, 이때 페이지를 생성하고 저장해놓기 때문에, 이후 요청들에 대해서는 다른 빌드시 생성된 페이지들과 마찬가지로 사용자가 매우 빠르게 페이지를 볼 수 있게 된다.
import axios from 'axios'
import _ from 'lodash';
export default function UserDetailPage({ user }) {
return (
<div>
{user.id} / {user.name} / {user.email}
</div>
)
}
export async function getStaticPaths() {
const { data: users } = await axios.get('https://jsonplaceholder.typicode.com/users');
const paths = _.map(_.slice(users, 0, 5), (user) => {
return { params: {id: _.toString(user.id)}};
});
return {
paths,
fallback: 'blocking'
}
}
export async function getStaticProps(context) {
const { id } = context.params;
const [{ data: user }] = await Promise.all([
axios.get(`https://jsonplaceholder.typicode.com/users/${id}`),
timeout(5000)
]);
return {
props: {
user
}
}
}
function timeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
fallback
이 'blocking'
인 경우 true
일때와 비슷하지만, 페이지 생성중에 사용자에게 fallback
버전의 페이지를 보여주지 않고, ssr처럼 동작해서 아무것도 미리 보여주지 않게된다.
위 코드 예시에서 볼 수 있듯이 fallback 상태
에 따라서 뭘 보여줄지 정의하지 않아도 되고, 빌드된 파일을 살펴보면 fallback: true
처럼 [id].html
을 만들어놓지도 않는다.
마찬가지로 getStaticProps
에 5초의 타임아웃을 걸어놓았기 때문에, 사용자는 빌드되지 않은 path로의 요청을 하는 경우 해당 시간동안 브라우저의 로딩 상태를 보게된다.
마찬가지로, 이후 같은 path로의 요청에는 빠른 응답을 할 수 있게 된다.