teklog

Next.js - ISR 공식문서 번역

2022/11/09

n°26

category : Next.js

학습 목적을 위해 Next.js의 Incremental Static Regeneration (ISR) 공식문서를 번역했습니다. 오타, 의역이 있을 수 있습니다. leetekwoo@gmail.com으로 피드백 주시면 감사하겠습니다.


---

Incremental Static Regeneration


Next.js는 웹사이트의 빌드 이후에 정적 페이지를 생성하거나 업데이트할 수 있게 해줍니다. Incremental Static Regeneration(ISR)은 사이트 전체를 리빌드할 필요없이 페이지 단위로 정적 생성을 사용할 수 있습니다. ISR을 이용하여 수백만의 페이지로 확장하면서 정적 생성의 이점을 유지할 수 있습니다.


ISR을 사용하기 위해서 getStaticProps의 props에 revalidate를 추가합니다.


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

/* 이 함수는 서버사이드에서 빌드 타임에 호출됩니다. 만일 revalidation이 가능하고, 새로운 요청이 들어온다면 서버리스 함수에서 다시 호출될 수 있습니다*/

export async function getStaticProps() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

  return {
    props: {
      posts,
    },

/* Next.js는 페이지 재생성을 시도할 것입니다.
 - 요청이 들어오면 
 - 최대 10초마다 1회씩 */
    revalidate: 10, // 초마다
  }
}

/* 이 함수는 서버사이드에서 빌드 타임에 호출됩니다. 만일 경로가 생성되지 않았다면, 서버리스 함수에서 다시 호출될 수 있습니다.*/
export async function getStaticPaths() {
  const res = await fetch('https://.../posts')
  const posts = await res.json()

// posts 기반으로 프리 렌더링하고자 하는 경로들을 얻습니다.
  const paths = posts.map((post) => ({
    params: { id: post.id },
  }))

/* 오직 이 경로들만 빌드 타임에 프리렌더 할 것입니다.
{ fallback: blocking }은 경로가 존재하지 않는다면 온디멘드 방식으로 페이지를 서버-렌더링 할 것입니다. */
  return { paths, fallback: 'blocking' }
}

export default Blog

빌드 타임에 프리 렌더링된 페이지의 요청이 이루어지면 초기에는 캐싱된 페이지를 보여줄 것입니다.


  • 최초 요청 이후 10초 전까지 페이지에 대한 모든 요청은 즉시 캐시됩니다.
  • 10초 후 다음 요청 시 캐시된(stale) 페이지가 계속 표시됩니다.
  • Next.js는 그 페이지의 재생성을 백그라운드에서 트리거합니다.
  • 페이지가 성공적으로 생성되면 Next.js는 캐시를 무효화하고 업데이트된 페이지를 보여줍니다. 백그라운드에서 재생성에 실패한다면 오래된 페이지는 여전히 변하지 않고 남아있습니다.


생성되지 않은 경로로 요청이 이루어질 때 Next.js는 첫 요청에 그 페이지를 서버-렌더링 할 것입니다. 이후의 요청들은 캐시로부터 정적 파일을 서빙할 것입니다. Vercel의 ISR은 캐시를 전역으로 유지하고 롤백을 처리합니다.


Note: 업스트림 데이터 공급자의 캐싱이 기본적으로 활성되었는지 확인하십시오. 비활성화로 설정해야 사용할 수 있습니다. (예시: useCdn: false) 그렇지 않다면 revalidation은 ISR 캐시로 새로운 데이터를 업데이트 할 수 없게 됩니다. Cache-control 헤더가 반환될 때 CDN (엔드포인트가 요청될 시)에서 캐싱이 발생할 수 있습니다.


On-demand Revalidation

온디멘드 Revalidation


revalidate의 시간을 60으로 설정하면 모든 방문자들은 1분 동안 동일한 사이트를 보게될 것입니다. 캐시를 무효화시키는 유일한 방법은 누군가 그 페이지를 1분이 지난 후에 방문하는 것입니다.


v12.2.0 버전에서부터 Next.js는 수동으로 특정한 페이지의 캐시를 삭제하기 위해 온디멘드 ISR을 제공합니다. 이는 다음과 같은 상황에서웹사이트의 업데이트를 더 쉽게 만듭니다.


  • headless CMS에서 생성된 컨텐츠가 생성되거나 업데이트될 때
  • E-커머스 메타데이터가 변경될 때 (가격, 설명, 카테고리, 리뷰, etc.)


온디멘디 revalidation을 사용하기 위해 getStaticProps 내부에서 revalidate를 특정지을 필요는 없습니다. revalidate가 없다면, Next.js는 기본 값인 false (revalidation 없음) 을 사용하고, revalidate()가 호출될 때만 페이지를 온디멘드 방식으로 revalidate할 것입니다.


Note: 미들웨어Middleware는 온디멘드 ISR 요청에서 실행되지 않습니다. 그 대신 revalidate하려는 동일한 경로에서 revalidate()를 호출할 것입니다. 예를 들어, pages/blog/[sug].js에서 /post-1을 /blog/post-1로 덮어쓴다면 res.revalidate(’/blog/post-1’)를 호출해야합니다.


Using On-Demand Revalidation

온디멘드 Revalidation 사용하기


첫째로, 당신의 Next.js 앱에서만 알 수 있는 비밀 토큰을 만드세요. 이 토큰은 revalidation API Route에 권한 없이 접근하는 것을 막아줍니다. 다음 URL structure를 따라 (수동이나 웹훅을 사용하여) 그 경로에 접근할 수 있습니다.

https://<your-site.com>/api/revalidate?secret=<token>

그 다음, 어플리케이션의 환경변수Environment Variable에 그 토큰을 더하시오. 마지막으로 revalidation API 경로를 만드세요.


// pages/api/revalidate.js

export default async function handler(req, res) {
  // 암호가 유효한 요청인지 확인하십시오. 
  if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
    return res.status(401).json({ message: 'Invalid token' })
  }

  try {
    // 이것은 덮여쓰여진 경로가 아닌 실제 경로가 되어야합니다.
    // 예시 :  "/blog/[slug]"라면 경로는"/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
		/* 에러가 발생한다면 Next.js는 마지막에 성공적으로 생성한 페이지를 계속 보여줍니다. */
    return res.status(500).send('Error revalidating')
  }
}

데모를 확인하여 온디멘드 revalidation을 확인하고 피드백을 남겨주세요.


Testing on-Demand ISR during development

온디멘드 ISR을 개발 중에 테스트하기


로컬에서 next dev를 실행하면 getStaticProps는 모든 요청에 호출됩니다. 온디멘드 ISR 설정이 정확한지 확인하려면 프로덕션 빌드를 생성하고 프로덕션 서버를 실행해야 합니다.


$ next build
$ next start

이후 정적 페이지들이 성공적으로 revalidate되었는지 확인할 수 있습니다.


Error handling and revalidation

에러 핸들링과 revalidation


백그라운드 재생성을 처리하면서 getStaticProps 내부에 에러가 발생하거나 수동으로 에러를 throw한다면, 마지막에 성공적으로 생성된 페이지가 여전히 보일 것입니다. 이어진 다음 요청에서 Next.js는 getStaticProps의 재호출을 시도할 것입니다.


export async function getStaticProps() {
 /* 이 요청이 예외처리되지 못한 에러를 throw한다면, Next.js는 현재 보여지는 페이지를 무효화 시키지 않고 getStaticProps를 다음 요청에 재시도할 것입니다. */

  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 하는 것이 좋습니다. */
    throw new Error(`Failed to fetch posts, received status ${res.status}`)
  }

// 요청이 성공된다면 posts를 반환하고 10초마다 revalidate합니다.
  return {
    props: {
      posts,
    },
    revalidate: 10,
  }
}

Self-hosting ISR

자체 호스팅 ISR


ISR은 next start를 사용할 시 자체-호스팅되는 Next.js 사이트에서 즉시 작동합니다. 이 접근을 Kubernetes나 HashiCorp Nomad 같은 컨테이너 오케스트레이터에 배포할 때 사용할 수 있습니다. 생성된 asset은 각 포드의 메모리에 저장됩니다. 즉 각 포드는 정적 파일의 사본을 가집니다. 오래된(stale) 데이터는 특정한 포드가 요청을 받을 때까지 보여질 것입니다.


모든 포드에 일관성을 유지하기 위해 인메모리 캐싱을 비활성화 해야합니다. 이는 Next.js 서버가 파일 시스템에서 ISR에 의해 생성된 asset만 활용하도록 알릴 것입니다.


Kubernetes 포드(또는 유사한 설정)의 공유 네트워크 마운트를 사용하여 다른 컨테이너 사이의 동일한 파일 시스템 캐시를 재사용할 수 있습니다. 같은 마운트를 공유하여, next/image 캐시를 담고있는 .next 폴더는 공유되고 재사용될 수 있습니다.

인메모리 캐싱을 비활성화하려면, next.config.js 파일의 isrMemoryCacheSize를 0으로 설정하십시오.


module.exports = {
  experimental: {
    // 기본값은 50MB입니다.
    isrMemoryCacheSize: 0,
  },
}

Note: 공유 마운트가 구성된 방식에 따라 캐시를 동시에 업데이트하려는 여러 포드 간의 경합 조건을 고려해야 할 수 있습니다. (쿠버네티스 지식이 전무하여 이해 불가)


Related

관련 문서

더 많은 정보를 위해 다음 섹션을 확인해보세요.


Data Fetching: getStaticPaths | Next.js