7.1
fetch
먼저 fetch를 사용해서 서버로부터 데이터를 받아오는 방법에 대해서 살펴보겠습니다. 일반적으로 웹애플리케이션을 개발할 때 Web API에서 제공하는 fetch를 사용하게 됩니다. Next.js는 이러한 네이티브 fetch Web API를 확장하여 서버에서 각 fetch 요청의 캐싱 및 재검증 동작을 구성할 수 있게 해줍니다. Next.js에서는 Server Components, Route Handlers, Server Actions에서 async/await와 함께 fetch를 사용할 수 있습니다.
아래 코드는 리액트 Server Components에서 fetch를 사용하여 데이터를 받아오는 예시 코드입니다.
async function getPosts() {
// 리턴 값은 직렬화(serialized)되지 않기 때문에 Date, Map, Set 등을 리턴할 수 있음
const response = await fetch('https://api.example.com/posts');
if (!response.ok) {
// 가장 가까운 `error.js` Error Boundary를 활성화 시킴
throw new Error('Failed to fetch posts');
}
return response.json();
}
// Server Components
async function Page() {
// 데이터 페칭
const posts = await getPosts();
return (
<main>
{posts.map((post) => {
return <div>{post.title}</div>;
})}
</main>
);
}
export default Page;
이처럼 Server Components에서는 서버측에서 곧바로 비동기 요청을 처리할 수 있다고 이해하면 됩니다.
일반적으로 캐싱은 자주 사용되는 데이터를 캐시에 저장해놓고 다시 사용함으로써 불필요한 연산을 줄이는 방법을 의미합니다. 데이터 페칭 관점에서의 캐싱은 서버로부터 받아온 데이터를 캐시에 저장해놓고, 동일한 요청이 왔을 때 다시 서버에 요청하지 않고 캐시에 저장된 데이터를 사용하는 것을 의미합니다.
현재 최신 버전의 Next.js에서는 기본적으로 fetch에서 반환된 데이터를 Data Cache에 자동으로 캐싱하지 않습니다. 만약 캐시를 강제로 사용하도록 하고 싶다면, 아래 예시 코드와 같이 cache 옵션의 값을 force-cache로 설정하면 됩니다.
fetch('https://api.example.com/posts', { cache: 'force-cache' });
이렇게 캐싱이 적용되면 애플리케이션을 빌드하는 시점이나 요청이 발생하는 시점에 가져온 데이터 캐싱하게 되며, 이렇게 캐싱된 데이터는 동일한 요청이 발생했을 때 사용됩니다. 물론 무조건 캐싱된 데이터를 사용하는 것은 아니며, 캐싱 전략에 따라서 작동하게 된다는 것을 기억하기 바랍니다.
캐싱과 항상 같이 나오는 개념이 바로 Revalidation입니다. Revalidation은 데이터를 재검증 및 갱신하는 것인데, 쉽게 말해서 최신 데이터를 가져와서 캐싱된 데이터를 덮어쓰는 것을 의미합니다. Revalidation이 필요한 이유는 캐싱된 데이터가 항상 최신 데이터가 아니기 때문입니다. 캐싱이 된 시점부터 시간이 조금이라도 지난 이후에는 오래된 데이터로 간주되는 것이죠.
만약 Revalidation을 하지 않고 계속해서 캐싱된 데이터를 사용하게 되면 오래된 데이터를 사용하게 되는 것이고, 사용자에게는 잘못된 데이터를 보여주는 결과를 초래할 수 있습니다. 그리고 이렇게 시간이 지난 데이터를 stale 데이터라고 부릅니다. 참고로 stale이라는 단어는 '썩은', '신선하지 않은'이라는 뜻을 갖고 있습니다. 직역해보자면 시간이 지나서 썩은 데이터를 stale 데이터라고 하는 것이죠.
캐싱된 데이터는 아래와 같은 두 가지 방식으로 재검증 할 수 있습니다.
Time-based Revalidation은 이름이 가진 의미대로 시간을 기반으로 재검증하는 방식입니다. 데이터가 캐싱되고 일정 시간이 지난 이후에 데이터를 자동으로 재검증하는 것이죠. 이 방식은 데이터가 자주 변경되지 않고 항상 최신 데이터가 필요한 것이 아닐 경우에 주로 사용합니다.
아래 코드는 특정 시간 간격(여기서는 3,600초)으로 데이터를 재검증하기 위한 예시 코드입니다. fetch의 next.revalidate 옵션에 시간 간격 값을 넣어줌으로써 캐싱된 데이터의 수명(초 단위)을 설정할 수 있습니다.
// 최대 1시간 이후 재검증
fetch('https://api.example.com/posts', { next: { revalidate: 3600 } });
그리고 Route Segment 내의 모든 fetch 요청을 재검증하려면 아래와 같이 Route Segment Config Options을 사용하면 됩니다.
// 값으로 false, 0, number 사용 가능
export const revalidate = 3600; // 3600초(1시간)마다 재검증
아래 그림은 Time-based Revalidation의 작동 방식을 나타난 것입니다. 이 그림에서는 시간 간격이 60초로 설정이 되어 있는 것을 볼 수 있습니다. 즉, 60초 이후에 들어오는 요청에 대해서는 재검증을 거치게 되는 것이죠.
만약 정적으로 렌더링된 Route에서 여러 fetch 요청이 있고 각각이 다른 재검증 주기를 가지고 있는 경우, 가장 짧은 시간이 모든 요청에 적용됩니다. 그리고 동적으로 렌더링된 Route에서는 각 fetch 요청이 독립적으로 재검증됩니다.
On-demand Revalidation은 필요한 경우에 직접 재검증을 요청하는 방식입니다. 태그 또는 경로를 기반으로 한 번에 데이터 그룹 전체를 재검증하게 됩니다. 시간 기반 재검증 방식에서는 정해진 시간 간격이 지나기 전까지는 최신 데이터를 보여줄 수 없습니다. 그래서 당장 최신 데이터를 보여줘야 할 필요가 있을 경우에는 On-demand Revalidation을 사용하는 것이 좋습니다.
On-demand Revalidation을 사용하려면 Server Actions 또는 Route Handlers 내에서 revalidatePath() 함수를 사용하여 특정 경로에 대해 재검증을 하거나, revalidateTag() 함수를 사용해서 특정 캐시 태그에 대해 재검증을 할 수 있습니다.
Next.js는 Route 전체에서 fetch 요청을 무효화하기 위한 캐시 태깅 시스템 (cache tagging system) 을 갖추고 있습니다. 그래서 fetch를 사용할 때 하나 이상의 태그로 캐시 항목에 태그를 할당할 수 있으며, next/cache 패키지의 revalidateTag() 함수를 호출하여 해당 태그와 관련된 모든 항목을 재검증할 수 있습니다.
아래 코드는 fetch 요청에 posts라는 캐시 태그를 추가하는 코드입니다.
async function Page() {
const res = await fetch('https://api.example.com/posts', {
next: { tags: ['posts'] },
});
const data = await res.json();
...
}
export default Page;
이후 아래 코드와 같이 Server Actions에서 posts라는 캐시 태그에 대해 revalidateTag() 함수를 호출함으로써 posts 태그가 지정된 fetch 호출을 모두 재검증할 수 있습니다.
'use server';
import { revalidateTag } from 'next/cache';
export default async function action() {
revalidateTag('posts');
}
NOTE. 재검증 과정에서의 에러 처리
만약 데이터를 재검증하는 과정에서 오류가 발생하면 마지막으로 캐싱된 데이터는 계속해서 사용되며, 다음 요청에서 데이터를 다시 재검증하게 됩니다.
마지막 업데이트: 2025년 10월 24일 02시 25분
이 문서의 저작권은 이인제(소플)에 있습니다. 무단 전재와 무단 복제를 금합니다.
On this page