Javascript

Next.js 15의 혁신적인 캐시 기능

드리프트2 2024. 10. 30. 20:54

Next.js 15의 혁신적인 캐시 기능

Next.js 15가 핫한 이슈로 떠오르고 있는 요즘, 가장 주목받는 기능 중 하나가 바로 대폭 개선된 캐시(Cache) 기능입니다.

 

새롭게 도입된 use cache 기능 덕분에 기존 Next.js의 복잡했던 캐시 전략이 훨씬 간편하고 효율적으로 변신했는데요.

 

이번 업데이트로 개발자들은 더욱 쉽고 유연하게 캐시 전략을 활용할 수 있게 되었고, 개발 경험도 크게 향상될 것으로 기대됩니다.

 

자, 그럼 지금부터 use cache 기능을 중심으로 Next.js 15의 캐시 기능에 대해 자세히 알아볼까요?

 

Next.js 14, 캐시 기능의 불편한 진실

1. fetch() 함수의 함정

Next.js 14에서는 fetch() 함수를 사용하면 기본적으로 데이터가 캐시되었습니다. 하지만 이는 fetch() 함수를 사용하지 않는 경우를 충분히 고려하지 않은 설계였습니다.

 

예를 들어, 로컬 데이터베이스에 접근하는 경우 캐시 제어가 제대로 이루어지지 않아 불필요한 데이터 중복이나 오래된 데이터 표시 문제가 발생할 수 있었습니다.

2. fetch() 이외의 함수 사용, 험난한 여정

fetch() 이외의 함수를 사용하여 데이터를 가져오는 경우, 개발자들은 복잡한 설정과 우회적인 방법을 사용해야 했습니다.

 

페이지(page.tsx)나 레이아웃(layout.tsx) 파일에서 config 설정을 변경하거나, export const dynamic, runtime, fetchCache, dynamicParams, revalidate 등의 속성을 직접 조작하는 등, Next.js의 캐시 기능을 제대로 활용하기 위해서는 상당한 노력과 전문 지식이 필요했습니다.

 

이러한 복잡성은 Next.js를 어렵게 만드는 주요 원인 중 하나였고, 많은 개발자들이 캐시 최적화에 어려움을 겪었습니다.

 

하지만 이제, Next.js 15에서는 이러한 복잡한 설정과 어려움을 모두 잊어도 됩니다!

 

Next.js 15, 캐시 기능의 혁명

1. use cache 하나면 끝!

Next.js 15에서는 페이지 상단에 use cache를 선언하는 것만으로 캐시 기능을 활성화할 수 있습니다.

// page.tsx
"use cache";

export default async function Page() {
  return fetch("..."); 
}

2. fetch() 함수, 이제는 선택!

use cache 지시어를 사용하여 캐시 여부를 명시적으로 지정하므로, fetch() 함수의 기본 동작 변경에 대한 논란도 해결되었습니다.

 

이제 개발자들은 fetch() 함수 이외의 다른 방법으로 데이터를 가져오더라도, use cache 지시어를 통해 원하는 캐시 전략을 적용할 수 있습니다.

 

3. Suspense 없이도 정적 페이지 구성 가능

Next.js 14에서는 fetch() 함수를 사용하는 컴포넌트를 Suspense로 감싸야 했습니다.

// page.tsx (Next.js 14)
import { Suspense } from 'react';

async function Component() {
  return fetch("...");
}

export default async function Page() {
  return <Suspense fallback="..."><Component /></Suspense>;
}

 

하지만 Next.js 15에서는 use cache 지시어를 사용하면 Suspense 없이도 정적 페이지를 구성할 수 있습니다.

 

가져온 모든 데이터가 자동으로 캐시되기 때문입니다.

// page.tsx (Next.js 15)
"use cache";

export default async function Page() {
  return fetch("...");
}

4. 부분적인 정적 콘텐츠 구성, 이제는 자유롭게!

루트 레이아웃(layout.tsx)에서 use cache를 선언하고, 동적인 콘텐츠를 표시하고 싶은 부분에서는 use cache를 생략하면 됩니다.

 

기본적으로는 SSG(Static Site Generation) 또는 SSR(Server-Side Rendering) 방식으로 페이지를 구성하고, 필요한 부분만 동적으로 처리하는 유연한 캐시 전략을 구현할 수 있습니다.

// layout.tsx
"use cache";

export default async function Layout({ children }) {
  const response = await fetch("...");
  const data = await response.json();
  return (
    <html>
      <body>
        <div>{data.notice}</div> 
        {children}
      </body>
    </html>
  );
}
// page.tsx
import { Suspense } from 'react'
async function Component() {
  return fetch(...)
}

export default async function Page() {
  return <Suspense fallback="..."><Component /></Suspense>
}

5. 함수도 캐시? 당연하죠!

Next.js 15에서는 함수도 캐시할 수 있습니다. 캐시하고 싶은 함수에 use cache를 선언하면 됩니다.

 

함수 캐시의 경우, 캐시 키(Cache Key)가 자동으로 설정되므로 개발자가 직접 관리할 필요가 없습니다. 이는 개발 생산성 향상에 큰 도움이 됩니다.

 

또한, JSX 내에서 await 키워드를 사용하여 함수를 호출할 수 있게 되었습니다. 앞으로 이러한 비동기적인 함수 호출 방식이 Next.js 개발의 주류가 될 것으로 예상됩니다.

// layout.tsx
async function getNotice() {
  "use cache";
  const response = await fetch("...");
  const data = await response.json();
  return data.notice; 
}

export default async function Layout({ children }) {
  return (
    <html>
      <body>
        <h1>{await getNotice()}</h1>
        {children}
      </body>
    </html>
  );
}

6. 캐시 태깅, 나만의 캐시 관리 전략

캐시 키를 자동으로 관리해 주는 Next.js 15지만, 개발자가 직접 관리해야 하는 캐시도 존재할 수 있습니다.

 

이럴 때는 cacheTag() 함수를 사용하여 캐시에 태그를 지정하고 관리할 수 있습니다.

// layout.tsx
import { cacheTag } from 'next/cache';

async function getNotice() {
  'use cache';
  cacheTag('my-tag'); 
}

 

태그가 지정된 캐시는 서버 액션(Server Action)에서 revalidateTag() 함수를 호출하여 선택적으로 삭제할 수 있습니다.

 

또한, 여러 개의 태그를 배열 형태로 지정하여 단일 캐시 항목에 여러 태그를 연결할 수도 있습니다.

cacheTag(['tag-one', 'tag-two']); 

 

7. 특정 캐시 삭제, 이제는 핀포인트!

 

캐시 태깅 기능은 게시글 목록처럼 자주 업데이트되는 데이터를 효율적으로 관리하는 데 유용합니다.

 

예를 들어, 게시글 목록을 가져오는 함수에 use cache를 선언하고 각 게시글에 고유한 태그를 지정합니다.

 

import { unstable_cacheTag as cacheTag } from 'next/cache';

async function getBlogPosts(page) {
  'use cache';
  const posts = await fetchPosts(page); 
  for (let post of posts) {
    cacheTag('blog-post-' + post.id); 
  }
  return posts;
}

 

이후 특정 게시글을 수정하면, 서버 액션에서 해당 게시글의 태그를 사용하여 revalidateTag() 함수를 호출하고 수정된 게시글의 캐시만 삭제할 수 있습니다.

 

8. 캐시 유효 기간 설정, ISR과 찰떡궁합!

cacheLife() 함수를 사용하면 캐시에 유효 기간을 설정하고, 원하는 시점에 캐시를 다시 검증(Revalidate)할 수 있습니다.

 

이는 ISR(Incremental Static Regeneration) 기능과 유사하지만, 캐시에 직접 적용된다는 점에서 차이가 있습니다.

 

cacheLife() 함수는 SWR(Stale-While-Revalidate) 전략과 같은 원리로 작동합니다.

 

즉, 캐시가 다시 검증되는 동안에는 이전 캐시 데이터를 반환하여 사용자에게 최대한 빠른 응답을 제공합니다.

 

cacheLife() 함수 설정:
다음과 같은 단위를 사용하여 캐시 유효 기간을 설정할 수 있습니다.

  • "seconds"
  • "minutes"
  • "hours"
  • "days"
  • "weeks"
  • "max"
// page.tsx
"use cache";
import { unstable_cacheLife as cacheLife } from 'next/cache';

export default async function Page() {
  cacheLife("minutes"); 
  return; 
}

 

 

커스텀 설정


next.config.js 파일에서 dynamicIO 플래그를 활성화하고, cacheLife 객체를 정의하여 사용할 수 있습니다.

// next.config.js
module.exports = {
  experimental: {
    dynamicIO: true,
    cacheLife: {
      blog: {
        stale: 3600, // 1 hour
        revalidate: 900, // 15 minutes
        expire: 86400, // 1 day
      },
    },
  },
};

 

stale, revalidate, expire 키는 각각 다음과 같은 역할을 합니다.

속성 설명 필수 여부
stale 숫자 클라이언트가 서버를 확인하지 않고 캐시된 값을 사용하는 기간 선택
revalidate 숫자 서버에서 캐시를 업데이트하는 빈도. 다시 검증하는 동안 이전 값이 제공될 수 있음. 선택
expire 숫자 값이 오래된 상태로 유지될 수 있는 최대 기간. revalidate 값보다 길어야 함. 선택

 

expire 값은 revalidate의 안전장치 역할을 합니다.

revalidate가 제대로 작동하지 않아 캐시가 업데이트되지 않더라도, expire 값이 설정되어 있으면 특정 기간이 지난 후 강제로 캐시가 다시 검증됩니다.

 

다음과 같이 cacheLife() 함수의 인자로 next.config.js에서 정의한 객체 이름을 전달하면 커스텀 설정 값을 사용할 수 있습니다.

import { unstable_cacheLife as cacheLife } from 'next/cache';

export async function getCachedData() {
  'use cache';
  cacheLife('blog'); 
  const data = await fetch('/api/data'); 
  return data;
}

9. 마무리

Next.js 15의 새로운 캐시 기능은 기존의 복잡한 설정과 어려움을 해결하고, 개발자에게 더욱 쉽고 유연한 캐시 관리 경험을 제공합니다.

 

특히, use cache 지시어를 통해 캐시 전략을 명시적으로 지정하고, 함수 캐시, 캐시 태깅, 캐시 유효 기간 설정 등 다양한 기능을 활용하여 웹 사이트 성능을 최적화할 수 있습니다.

 

앞으로 Next.js 15의 혁신적인 캐시 기능이 국내 개발자들에게도 널리 알려지고 활용되기를 기대합니다!