[Next.js] Data Fetching > Incremental Static Regeneration

장유진·2022년 10월 26일
1

Next.js

목록 보기
6/6

원본: https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration


Next.js에서는 빌드 이후에 정적 페이지를 생성하거나 업데이트하는 것이 가능하다. Incremental Static Regeneration(ISR)은 전체 사이트를 다시 빌드할 필요 없이 페이지 별로 static 생성이 가능하도록 해준다. ISR을 사용하면 수백만 페이지로 사이트를 확장하면서도 static의 이점을 유지할 수 있다.

ISR을 사용하기 위해서는ㄴ getStaticProps에 revalidate를 추가해야 한다.

function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// revalidation is enabled and a new request comes in
export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },
    // Next.js will attempt to re-generate the page:
    // - When a request comes in
    // - At most once every 10 seconds
    revalidate: 10, // In seconds
  }
}

// This function gets called at build time on server-side.
// It may be called again, on a serverless function, if
// the path has not been generated.
export async function getStaticPaths() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  // Get the paths we want to pre-render based on posts
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

  // We'll pre-render only these paths at build time.
  // { fallback: blocking } will server-render pages
  // on-demand if the path doesn't exist.
  return { paths, fallback: 'blocking' }
}

export default Blog

빌드할 때 사전 렌더링된 페이지에 대한 요청이 들어오면 처음에는 캐시된 페이지가 보여진다.

  • 첫 요청과 10초 전 사이의 모든 요청들은 즉시 캐시된다.
  • 10초 후의 요청은 여전히 캐시된 페이지를 보여준다.
  • Next.js는 백그라운드에서 페이지를 재생성한다.
  • 페이지가 성공적으로 생성되면 Next.js는 캐시를 무효화하고 업데이트된 페이지를 보여준다. 백그라운드 재생성이 실패하면 이전 페이지는 변경되지 않는다.

생성되지 않은 경로에 대한 요청이 발생하면 Next.js는 그 페이지를 서버에서 렌더링한다. 이후의 요청들은 캐시에서 static 파일을 제공한다. Vercel의 ISR은 캐시를 전역적으로 유지하고 롤백을 처리한다.

On-demand Revalidation

만약 revalidate를 60초로 설정하면, 모든 방문자들은 1분동안 동일한 버전의 사이트를 보게 된다. 캐시를 무효화하는 유일한 방법은 1분이 지난 후에 누군가가 해당 페이지를 방문하는 것이다.

12.2.0 버전부터 Next.js는 On-Demand ISR을 제공하여 특정 페이지에 대한 캐시를 수동으로 제거할 수 있게 된다. 이렇게 하면 다음과 같은 경우 사이트를 더 쉽게 업데이트할 수 있다.

  • headless CMS로부터 온 데이터가 생성되거나 업데이트 되었을 때
  • 이커머스 메타데이터가 변경되었을 때 (가격, 설명, 카테고리, 리뷰 등)

On-demand revalidation을 사용하기 위해서 getStaticProps 내부에서 revalidate를 지정할 필요는 없다. revalidate가 생략되면 Next.js는 revalidate를 기본값인 false로 설정하고 revalidate()가 호출될 때의 페이지만 재검증한다.

On-demand Revalidation 사용하기

먼저 Next.js 앱의 비밀 토큰을 생성한다. 이 토큰은 revalidation API 경로에 대한 무단 엑세스를 방지하는 데 사용된다. https://<your-site.com>/api/revalidate?secret=<token>를 사용하여 경로에 접근할 수 있다.

그 다음 토큰을 환경 변수로 애플리케이션에 추가하고 revalidation API 경로를 추가한다.

// pages/api/revalidate.js

export default async function handler(req, res) {
  // Check for secret to confirm this is a valid request
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: 'Invalid token' })
  }

  try {
    // this should be the actual path not a rewritten path
    // e.g. for "/blog/[slug]" this should be "/blog/post-1"
    await res.revalidate('/path-to-revalidate')
    return res.json({ revalidated: true })
  } catch (err) {
    // If there was an error, Next.js will continue
    // to show the last successfully generated page
    return res.status(500).send('Error revalidating')
  }
}

개발 중 On-demand ISR 테스트하기

next dev로 로컬에서 실행할 때 getStaticProps는 모든 요청마다 호출된다. On-demand ISR 설정이 정확한지 확인하기 위해서는 아래 커맨드로 운영 빌드 후 운영 서버를 시작해야 한다.

$ next build
$ next start

에러 처리 및 재검증

백그라운드에서 페이지를 재생성하는 동안 getStaticProps 내부에서 에러가 발생하거나 에러를 throw하여 발생시킨다면 마지막으로 성공적으로 생성된 페이지가 계속 표시된다. 다음 요청이 들어오면 getStaticProps 호출을 다시 시도한다.

export async function getStaticProps() {
  // If this request throws an uncaught error, Next.js will
  // not invalidate the currently shown page and
  // retry getStaticProps on the next request.
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  if (!res.ok) {
    // If there is a server error, you might want to
    // throw an error instead of returning so that the cache is not updated
    // until the next successful request.
    throw new Error(`Failed to fetch posts, received status ${res.status}`)
  }

  // If the request was successful, return the posts
  // and revalidate every 10 seconds.
  return {
    props: {
      posts,
    },
    revalidate: 10,
  }
}

Self-hosting ISR

ISR은 next start를 사용할 때 기본적으로 자체 호스팅 된 Next.js 사이트에서 작동한다.

Kubnernetes나 HashiCorp Nomad와 같은 컨테이너 오케스트레이션에 배포할 때 이와 같은 접근 방식을 사용할 수 있다. 기본적으로 생성된 asset은 각 pod의 인메모리에 저장된다. 이는 각 pod가 static 파일의 복사본을 가지고 있다는 것을 의미한다. 어떤 pod는 요청을 받을 때까지 오래된 데이터가 표시될 수 있다.

모든 pod들 사이에 consistency를 보장하기 위해서는 인메모리 캐싱을 비활성화하면 된다. 그러면 파일 시스템에서 ISR에 의해 생성된 asset만 활용하도록 Next.js 서버에 알리게 된다.

Nubernetes pod에서 공유 네트워크 마운트를 사용하여 다른 컨테이너들 사이에서 같은 파일 시시스템 캐시를 사용할 수 있다. 동일한 마운트를 공유하면 next/image 캐시를 포함한 .next 폴더도 공유되어 재사용될 수 있다.

인메모리 캐싱을 비활성화 하려면 next.config.js 파일에서 isrMemoryCacheSize를 0으로 설정한다.

profile
프론트엔드 개발자

0개의 댓글