[Next] getStaticPaths fallback 설정 사용

김정민·2022년 6월 20일
2

이번에 Next에서 동적라우팅으로 getStaticPaths가 존재한다는걸 확인하고 사용하다 중요한 설정인 fallback관련을 확실히 구별해서 작성해보려합니다.

Next 에서 기존 동적라우팅 방법말고, getStaticPaths를 이용하면 빌드타임에 해당 페이지를 생성할 수 있습니다. 해당 페이지 컴포넌트에 getStaticPathsgetStaticProps를 사용하면 가능한데, getStaticPaths에 관해 자세히 살펴보겠습니다.

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 페이지를 빌드타임에 생성하게 됩니다.

paths

어떤 path의 페이지들을 빌드 타임에 생성할지 정하는 배열이다.

paths 배열에서 각각의 params 값은 페이지 이름에 있는 파라미터와 일치해야한다.

여기서 주의할 점들은 다음과 같다.

  • 만약 파일명이 pages/users/[userId].js 였다면, params 객체도 userId 키값을 가지고 있어야한다.
  • 파일명이 pages/[...slug]와 같이 catch-all 라우트를 이용중인 경우, params 객체는 slug 배열을 가지고 있어야한다.
  • 파일명이 pages/[[..slug]]와 같이 optional catch-all 라우트를 이용중인 경우, null / [] / undefined / false 값을 넣어주면 루트 라우트를 렌더링하게 됩니다.

fallback

여기서 이해하느라 애먹었던 부분이 존재하는데, 그것이 바로 fallback 설정입니다.
빌드 타임에 생성해놓지 않은 path로 요청이 들어온 경우 어떻게 할지 정하는 boolean 또는 'blocking' 값입니다.

false

getStaticPaths로 반환되지 않은 path외 모든 path는 자동으로 404페이지를 라우팅해줍니다.

이럴때 false를 넣는다.

  • 동적라우팅의 path가 적어서 적은 수의 path만 프리렌더링 해야하는 경우
  • 새로운 페이지가 추가 될 일이 많지 않은 경우
    - 만약 새로운 페이지가 자주 추가 된다면, 추가될때마다 다시 빌드해줘야한다. (주기적으로는 getStaticprops revalidate로 설정도 가능할 것 같긴하다)

true

true가 되면 getStaticProps의 동작이 변한다,

  1. getStaticPaths가 반환한 path들은 빌드 타임에 HTML로 렌더링된다
  2. 이외의 path들에 대한 요청이 들어온 경우, 404 페이지를 반환하지 않고, 페이지의 "fallback" 버전을 먼저 보여준다
  3. 백그라운드에서 Next js가 요청된 path에 대해서 getStaticProps 함수를 이용하여 HTML 파일과 JSON 파일을 만들어낸다
  4. 백그라운드 작업이 끝나면, 요청된 path에 해당하는 JSON 파일을 받아서 새롭게 페이지를 렌더링한다. 사용자 입장에서는 [ fallback → 풀 페이지 ]와 같은 순서로 화면이 변하게된다.
  5. 새롭게 생성된 페이지를 기존의 빌드시 프리렌더링 된 페이지 리스트에 추가한다. 같은 path로 온 이후 요청들에 대해서는 이때 생성한 페이지를 반환하게된다.

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로 설정해주면 이후 요청이 오는 것에 따라서 정적 페이지들을 추가하게 된다
      → 빌드 시간도 단축하고, 대부분 사용자들의 응답 속도도 단축할 수 있다

blocking

전반으로 true일 경우와 비슷하게 동작하지만, 최초 만들어놓지않은 path에 대한 요청이 들어온 경우 fallback 상태를 보여주지 않고 SSR처럼 동작한다. 이후 true 옵션과 같이 기존의 정적 페이지 리스트에 새로 생성한 페이지를 추가한다.

fallback 예시

fallback:false

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페이지

fallback: true

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));
}

fallbacktrue인 경우에는 사전에 빌드하지 않은 path에 대해서 요청이 들어올시 첫 요청 시기에 getStaticProps 호출을 통해 페이지를 만들고, 빌드된 path 리스트에 추가한다. 페이지가 아직 만들어지지 않았을 때, 페이지의 fallback 버전을 렌더링한다.

위 코드 예시에서는 의도적으로 getStaticProps의 실행을 늦추기 위해 timeout 함수를 선언해서 사용했다. getStaticProps의 실행에는 최소 5초가 걸리게된다.
즉, 새로운 path요청은 5초 이상의 fallback 상태이다.

빌드 후에 파일을 확인해보면, [id].html이 추가로 생성되는데, 이 파일이 /users/[id] 라우트로의 fallback 버전이 된다. 실제로 파일을 열어보면 아래와 같이 페이지 컴포넌트에서 정의한 fallback 버전을 볼 수 있다. 참고로, fallback 버전이 지정되지 않으면 빌드과정에서 오류가 발생한다.

사전에 빌드되지 않은 path에 대한 요청이 들어가게 되면,

위와 같이 페이지 생성이 완료되기 전까지 fallback 버전을 보여주다가, 생성이 완료되면 생성된 페이지를 저장하고, 보여주게 된다.

이렇게 새로운 path로의 첫 요청은 오래 걸리지만, 이때 페이지를 생성하고 저장해놓기 때문에, 이후 요청들에 대해서는 다른 빌드시 생성된 페이지들과 마찬가지로 사용자가 매우 빠르게 페이지를 볼 수 있게 된다.

fallback: 'blocking'

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로의 요청에는 빠른 응답을 할 수 있게 된다.

0개의 댓글