본문 바로가기

Study/NextJS 공식문서

[4주 차] Data Fetching, Caching, and Revalidating

Data Fetching은 모든 애플리케이션의 핵심이다. NextJS에서 데이터를 가져오는 방법은 fetch, Route Handler, 타사 라이브러리 등이 있다.

 

서버에서 fetch 사용

NextJS는 기본 fetch Web API를 확장해, 서버의 fetch 요청에 대한 Caching 및 Revalidating 동작을 사용할 수 있게 한다. 또한, 리액트 컴포넌트 트리를 랜더링 하는 동안 fetch 요청을 Memoize한다. fetch를 async/await와 함께 서버 컴포넌트에서 사용할 수 있다.
// 서버 컴포넌트 
export default async function Page() {
  const data = await getData()

  return <main></main>
}
// 데이터를 불러오는 함수
async function getData() {
  const res = await fetch('https://api.example.com/...')
 
  if (!res.ok) {
    throw new Error('Failed to fetch data')
  }
 
  return res.json()
}

 

Memoize & Caching 

기본적으로 서버는 In-Memory Request Memoization(Memoize), Persistent Data Cache(Caching), Data Source(fetch)의 순서로 데이터를 요청하며, 각 단계에서 히트되면 이후 프로세스는 중단된다.

Memoize는 랜더링 동안 서버 메모리에 데이터를 저장하여, 동일 요청에 대해 중복된 fetch 호출을 방지한다. 서버가 재시작되거나 랜더링이 종료되면 데이터는 사라지기 때문에 단기적이고 즉각적인 성능 향상에 초점을 두고 있다.

Caching은 이전에 요청한 데이터를 Data Cache에 캐싱하여, 모든 요청마다 데이터를 re-fetch 하지 않도록 한다. NextJS에서는 Caching하는 것이 기본값이며 fetch에서 캐싱하지 않도록 'no-store'를 설정하면 요청마다 새로운 데이터를 가져오는 SSR이 되고, 기본값이라면 빌드 타임 때의 데이터를 유지하는 SSG 또는 ISR이 된다.

// SSG, ISR
fetch('https://...')

// SSR
fetch('https://...', { cache: 'no-store' })

 

Revalidating

Revalidating은 캐시를 제거하고 최신 데이터를 다시 가져오는 프로세스다. 이는 데이터가 변경되어 최신 정보를 표시해야할 때 유용하다.

Revalidating은 일정 시간이 지나면 데이터를 자동으로 Revalidating하는 시간 기반 재검증, 이벤트를 기반으로 데이터를 수동으로 Revalidating하는 요청 기반 재검증이 있다.

 

시간 기반 재검증

일정 간격으로 데이터를 재검증하려면 fetch의 next.revalidate 옵션을 사용하여 리소스의 캐시 수명(초)을 설정할 수 있다. 페이지에서 여러 개의 ISR fetch 요청이 각각 다른 revalidate 값을 가지는 경우 가장 낮게 설정된 revalidate 값으로 모두 통일되기 때문에 일반적으로 layout.tsx 또는 page.tsx 상단에 revalidate를 선언하는 것이 일반적이다. SSR의 요청 리사이클에는 영향을 받지 않는다.
// app/products/page.js
export const revalidate = 3600; // 페이지 전체에 대한 기본 revalidate 값 (1시간마다)

export default async function ProductsPage() {
  const user = await fetch('https://api.example.com/user', {
    cache: 'no-store', // 매 요청마다 새로운 데이터 가져오기
  }).then((res) => res.json());
  
  const products = await fetch('https://api.example.com/products', {
    next: { revalidate: 600 }, // 10분마다 갱신
  }).then((res) => res.json());
  
  // 아래 fetch도 10분마다 갱신된다.
  const reviews = await fetch('https://api.example.com/reviews', {
    next: { revalidate: 1800 }, // 30분마다 갱신
  }).then((res) => res.json());

  return (
    <div>
      <h1>Products</h1>
      <ul>
        {products.map((product) => (
          <li key={product.id}>{product.name}</li>
        ))}
      </ul>
    </div>
  );
}

 

요청 기반 재검증

요청 기반 재검증을 하려면 revalidateTag와 revalidatePath, next.tags를 사용하면 된다. 이 기능을 활용하면 사용자가 요청할 때 또는 외부 이벤트 발생 시 데이터를 즉시 최신 상태로 업데이트 할 수 있다.
// app/dashboard/page.tsx
export default async function Dashboard() {
  const res = await fetch('https://api.example.com/posts', {
    next: { tags: ['collection'] } // 'collection' 태그를 추가
  });
  const data = await res.json();

  return <div>{JSON.stringify(data)}</div>;
}
// app/dashboard/action.ts
'use server'
import { revalidateTag } from 'next/cache'
 
export default async function action() {
  revalidateTag('collection')
}
// app/dashboard 아래 클라이언트 컴포넌트
'use client'
import { useTransition } from 'react';
import action from './action';

export default function ClientComponent() {
  const [isPending, startTransition] = useTransition(); // UI 블로킹 방지

  return (
    <div>
      <button
        onClick={() => startTransition(action)}
        disabled={isPending}
      >
        {isPending ? 'Updating...' : 'Refresh Data'}
      </button>
    </div>
  );
}
// app/dashboard 아래 클라이언트 컴포넌트
'use client'
import { useFormState } from 'react';
import action from './action';

export default function ClientComponent() {
  const [state, formAction] = useFormState(action, null);

  return (
    <form action={formAction}>
      <button type="submit">Refresh Data</button>
    </form>
  );
}

 

Caching 비활성화 방법

// cache: 'no-store' 옵션 사용
fetch('https://api.example.com/data', { cache: 'no-store' })

// revalidate: 0 옵션 사용
fetch('https://api.example.com/data', { next: { revalidate: 0 } })

// fetch가 POST 요청을 보낼 때
fetch('https://api.example.com/data', { method: 'POST' })

// 요청에 headers 또는 cookies가 포함된 경우
fetch('https://api.example.com/data', {
  headers: { Authorization: `Bearer ${token}` }
});

 

Route Handler

Route Handler를 사용한 Date Fetching 방법은 여기를 참고한다.

'Study > NextJS 공식문서' 카테고리의 다른 글

[4주 차] Patterns and Best Practices  (1) 2025.01.31
[4주 차] Server Action  (0) 2025.01.31
[3주 차] Middleware  (0) 2025.01.31
[3주 차] Route Handlers  (0) 2025.01.27
[3주 차] Parallel Routes  (0) 2025.01.27