fetching: (ISR)Incremental Static Regeneration

hwisaac·2023년 3월 11일
0

Next.js

목록 보기
11/29

Next.js는 정적 페이지를 생성하거나 업데이트할 수 있도록 해줍니다. ISR(Incremental Static Regeneration)을 사용하면 전체 사이트를 다시 빌드할 필요 없이 각 페이지별로 정적 생성을 사용할 수 있습니다. ISR을 사용하면 정적으로 유지하면서 수백만 페이지까지 확장할 수 있습니다.

참고: 실험 중인 edge 런타임은 현재 ISR과 호환되지 않습니다. 그러나 캐시 제어 헤더를 수동으로 설정하여 stale-while-revalidate을 활용할 수 있습니다.

ISR을 사용하려면 getStaticProps에 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초마다 한 번
    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는 전역적으로 캐시를 유지하고 롤백을 처리합니다.(https://vercel.com/docs/concepts/incremental-static-regeneration/overview?utm_source=next-site&utm_medium=docs&utm_campaign=next-website)

참고: 상위 데이터 공급자에서 캐싱이 기본적으로 활성화되어 있는지 확인하세요. 그렇지 않으면 (예: useCdn: false로 비활성화) 유효성 검사에서 ISR 캐시를 업데이트하기 위해 신선한 데이터를 가져올 수 없을 수 있습니다. 캐싱은 Cache-Control 헤더를 반환할 때 CDN에서 발생할 수 있습니다.

On-Demand 재유효화

재유효화 시간을 60으로 설정하면 모든 방문자는 1분 동안 동일한 생성된 버전의 사이트를 볼 수 있습니다. 캐시를 무효화하는 유일한 방법은 1분이 경과한 후 해당 페이지를 방문하는 사람입니다.

Next.js는 v12.2.0 이상부터 On-Demand ISR을 지원하여 특정 페이지에 대한 Next.js 캐시를 수동으로 삭제할 수 있습니다. 이를 통해 다음과 같은 경우에 사이트를 업데이트하기 쉬워집니다.

  • Headless CMS의 콘텐츠가 생성되거나 업데이트될 때
  • 전자 상거래 메타 데이터 변경 (가격, 설명, 카테고리, 리뷰 등)

getStaticProps 내부에서 On-Demand 재유효화를 사용하려면 유효성 검사를 지정할 필요가 없습니다. 유효성 검사가 생략된 경우 Next.js는 기본값인 false (재유효화 없음)을 사용하고, revalidate()가 호출될 때만 페이지를 On-Demand로 재유효화합니다.

참고: On-Demand ISR 요청에 미들웨어가 실행되지 않습니다. 대신, 정확한 경로에서 revalidate()를 호출하세요. 예를 들어, pages/blog/[slug].js와 /post-1 -> /blog/post-1에서 rewrite가 있는 경우 res.revalidate('/blog/post-1')을 호출해야 합니다.

Using On-demand Revalidation

먼저, Next.js 앱에서만 알 수 있는 비밀 토큰을 만듭니다. 이 비밀 토큰은 재유효화 API 라우트에 대한 무단 액세스를 방지하기 위해 사용됩니다. 다음 URL 구조를 사용하여 라우트에 액세스할 수 있습니다(수동으로 또는 웹훅을 사용하여):

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

그 다음, 이 비밀을 환경 변수로 앱에 추가합니다. 마지막으로 재유효화 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이 어떻게 작동하는지 확인하고 피드백을 제공하려면 데모(https://on-demand-isr.vercel.app/)를 확인하세요.

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

next dev로 로컬에서 실행할 때마다 getStaticProps가 호출됩니다. On-Demand ISR 구성이 올바른지 확인하려면 프로덕션 빌드를 만들고 프로덕션 서버를 시작해야 합니다:

$ next build
$ next start

그런 다음 정적 페이지가 성공적으로 재유효화되었는지 확인할 수 있습니다.

오류 처리 및 재유효화

만약 getStaticProps에서 백그라운드 재생성 작업을 처리하는 동안 오류가 발생하거나, 직접 오류를 던지면 마지막으로 성공적으로 생성된 페이지가 계속해서 보여집니다. 그러나 다음 요청에서 getStaticProps가 다시 시도됩니다.

export async function getStaticProps() {
  // 만약 이 요청에서 uncaught error가 발생하면 Next.js는 현재 표시된 페이지를 무효화하지 않고,
  // 다음 요청에서 getStaticProps를 다시 시도합니다.
  const res = await fetch('https://.../posts');
  const posts = await res.json();

  if (!res.ok) {
    // 만약 서버 오류가 발생하면, 캐시가 업데이트되지 않도록
    // 반환하는 대신 에러를 던질 수 있습니다.
    // 다음 성공적인 요청까지 캐시가 업데이트되지 않습니다.
    throw new Error(`Failed to fetch posts, received status ${res.status}`);
  }

  // 요청이 성공하면 게시물을 반환하고 10초마다 재유효화합니다.
  return {
    props: {
      posts,
    },
    revalidate: 10,
  };
}

ISR 자체 호스팅

자체 호스팅되는 Next.js 사이트에서 ISR(Incremental Static Regeneration)는 next start를 사용할 때 기본적으로 작동합니다.

KubernetesHashiCorp Nomad와 같은 컨테이너 오케스트레이터에 배포할 때 이 방법을 사용할 수 있습니다. 기본적으로 생성된 자산은 각 팟에서 인메모리에 저장됩니다. 이것은 각 팟이 정적 파일의 자체 복사본을 가지고 있다는 것을 의미합니다. 해당 특정 팟이 요청을 받을 때까지 오래된 데이터가 표시될 수 있습니다.

모든 팟에서 일관성을 보장하기 위해 인메모리 캐싱을 비활성화할 수 있습니다. 이것은 Next.js 서버가 파일 시스템에서만 ISR에서 생성된 자산을 사용하도록 알립니다.

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

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

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

참고: 공유 마운트가 구성되어 있는 방식에 따라 여러 파드에서 캐시를 동시에 업데이트하려는 경합 상황을 고려해야 할 수 있습니다.

독립형 ISR(Self-hosting ISR)

Incremental Static Regeneration (ISR)next start를 사용하면 독립형 Next.js 사이트에서 기본적으로 작동합니다.

이 방법은 Kubernetes 또는 HashiCorp Nomad와 같은 컨테이너 오케스트레이터에 배포할 때 사용할 수 있습니다. 기본적으로 생성된 에셋은 각 파드(pod)에서 인메모리로 저장됩니다.

이것은 각 파드가 고유한 정적 파일의 복사본을 가지고 있다는 것을 의미합니다. 만료된 데이터가 요청이 발생할 때까지 표시될 수 있습니다.

모든 파드에서 일관성을 보장하려면 인메모리 캐싱을 비활성화할 수 있습니다. 이렇게 하면 Next.js 서버가 파일 시스템에서 ISR에서 생성된 에셋만 활용하도록 알려줍니다.

Kubernetes 파드(또는 유사한 설정)에서 공유 네트워크 마운트를 사용하여 동일한 컨테이너 간에 동일한 파일 시스템 캐시를 재사용할 수 있습니다. 동일한 마운트를 공유하면 next/image 캐시를 포함하는 .next 폴더도 공유되고 재사용됩니다.

인메모리 캐싱을 비활성화하려면, next.config.js 파일에서 isrMemoryCacheSize0으로 설정합니다.

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

참고: 공유 마운트가 구성되어 있는 방식에 따라 여러 파드에서 캐시를 동시에 업데이트하려는 경합 상황을 고려해야 할 수 있습니다.

0개의 댓글