Next.js(v15) 에서 데이터 캐시하기

sujin·2025년 2월 14일
post-thumbnail

고백하자면 저는 next.js13 버전만 사용해봤습니다.😅 14버전이 나왔을 때도 공부해야지 공부해야지 미루기만 하고 최근에서야 앱라우터 와 서버 컴포넌트를 사용하는 중입니다...ㅎㅎ

막상 사용해보니 이렇게 효율적일 수가 없더라구요 진작에 공부해서 써볼껄 이라는 후회가 들었습니다. next.js 15버전을 공부하던 중에 가장 인상 깊었던 것은 캐시 기능이었고 새로워진 next.js는 같은 일 계속 시키는거 정말 싫어하는 구나라고 느꼈습니다.

그래서 오늘은 next.js의 데이터 캐시하는 방법에 대해서 정리해보려고 합니다~!


1️⃣ 데이터 페칭

먼저, 데이터 페칭에 대해서 어떻게 이뤄지는지 간략히 알아보도록 하겠습니다.

Page Router 버전에서 데이터 페칭

  • 페이지 파일에서 SSR, SSG, Dynamic SSG를 통해 서버에서 데이터를 가져와서 page에 props로 데이터를 전달합니다.
  • 모든 컴포넌트가 클라이언트 컴포넌트로 동작합니다. (= 컴포넌트가 서버와 클라이언트에서 모두 실행된다는 것을 의미합니다.)
  • 최상단에서 데이터를 필요로 하는 페이지까지 props로 계속 전달해줘합니다. (props drilling 문제)

App Router 버전에서 데이터 페칭

  • 서버 컴포넌트에서 async - await 키워드와 fetch 메서드를 활용해서 데이터를 직접 불러오는 데이터 페칭 로직을 만들 수 있습니다.
     export async function Page(props){
    	 const data = await fetch('...');
    	 
    	 return <div>...</div>
	}
  • 하지만 클라이언트 컴포넌트에서는 async - await 키워드 를 사용할 수 없습니다.
  • 데이터가 필요한 곳에서 직접 불러오는 것을 권장합니다.

🧐 async - await 키워드 사용이 서버 컴포넌트에서는 되지만, 클라이언트 컴포넌트에서는 안되는 이유

  • 서버 컴포넌트는 서버에서 데이터를 미리 가져와서 HTML을 렌더링할 수 있기 때문입니다. 서버 측에서는 비동기 작업이 완료된 후 렌더링을 처리하므로, 비동기 작업을 문제없이 사용할 수 있습니다.
  • 반면, 클라이언트 컴포넌트는 브라우저에서 실행되며, 랜더링 시에 비동기 작업을 바로 처리할 수 없기 때문에 제대로 렌더링이 이루어지지 않을 수 있습니다.

☝🏻 주의사항
데이터 패칭을 위한 환경 변수를 설정할 때 .env 파일에서 NEXT_PUBLIC 이라는 접두사를 붙이지 않으면 next 는 자동으로 해당 환경변수를 서버측에서만 접근 가능한 것으로 인식하여 해당 환경변수에 대해 클라언트 컴포넌트에서는 접근이 불가능합니다!


2️⃣ 데이터 캐시

Next.js에서 데이터 캐시란

  • fetch 메서드를 활용해 불러운 데이터를 Next 서버에서 보관하는 기능을 말합니다.
  • 영구적으로 데이터를 보관하거나, 특정 시간을 주기로 갱신 시키는 것도 가능합니다.
  • 캐시의 목적은 불필요한 데이터 요청의 수를 줄여서 웹 서비스의 성능을 개선하는 것이라고 할 수 있습니다.

캐시 옵션

📢 Next 서버에서 발생하는 데이터 패칭을 로그로 남기는 설정

  import type { NextConfig } from "next";
  const nextConfig: NextConfig = {
    	logging: {
    		fetches: {
    			fullUrl: true,	
    		},
    	},
    };
  export default nextConfig;
  • next.config.ts 파일을 위와 같으 설정하면 터미널에서 데이터 패칭에 대한 로그를 확인할 수 있습니다!
const response = await fetch('~/api', {cache: "force-cache});
  • {cache: "force-cache"}
    • 요청의 결과를 무조건 캐싱합니다.
    • 한번 호출 된 이후에는 다시는 호출되지 않습니다.
  • {cache: "no-store"}
    • 기본값으로, 데이터 페칭의 결과를 저장하지 않습니다.
    • 캐싱을 아예 하지 않아 매번 데이터를 요청합니다.
  • {next: {revalidate: 3}}
    • 특정 시간을 주기로 캐시를 업데이트합니다.
    • 마치 Page Router의 ISR방식과 비슷하다고 생각하면 됩니다.
  • {next: {tags: ['a']}}
    • on-Demand Revalidate
    • 요청이 들어왔을 때 데이터를 최신화하는 방식입니다.

예시

책 리스트 불러오는 API로 예시를 살펴보도록 하겠습니다!

async function AllBooks() {
  const response = await fetch(
    `${process.env.NEXT_PUBLIC_API_SERVER_URL}/book`,
    { cache: "no-store" } // 이 부분을 원하는 캐시 옵션으로 설정합니다!
  );
  if (!response.ok) {
    return <div>오류가 발생했습니다...</div>;
  }
  const allBooks: BookData[] = await response.json();

  return (
    <div>
      {allBooks.map((book) => (
        <BookItem key={book.id} {...book} />
      ))}
    </div>
  );
}

export default function Home() {
  return (
    <div>
      <section>
        <h3>등록된 모든 도서</h3>
        <AllBooks />
      </section>
    </div>
  );
}
  • {cache: "force-cache"} 로 설정했을 때

  • {cache: "no-store"} 로 설정했을 때


3️⃣ 리퀘스트 메모이제이션

마지막으로 리퀘스트 메모이제이션이란 "요청을 기억한다." 라는 뜻으로, 중복된 요청이 발생하지 않도록 캐싱하는 것을 말합니다. 그렇다면 여기서 "데이터 캐시랑 뭐가 다른거지?" 라는 의문을 생길 수 있습니다.

✋🏻 데이터 캐시 VS 리퀘스트 메모이제이션

  • 데이터 케시는 백엔드 서버로부터 불러온 데이터를 거의 영구적으로 보관하기 위해 사용됩니다. 따라서 서버 가동중에는 영구적으로 보관됩니다.

  • 리퀘스트 메모이제이션은 하나의 페이지를 렌더링 하는 동안에 중복된 API 요청을 캐싱하기 위해 존재합니다. 따라서, 랜더링이 종료되면 모든 캐시가 소멸됩니다.

결론적으로 데이터 캐시는 지속적으로 유지되는 반면, 리퀘스트 메모이제이션은 렌더링 동안에만 유지된다고 정리할 수 있습니다.

🤷🏻‍♀️ 리퀘스트 메모이제이션의 등장 배경

리퀘스트 메모이제이션이 필요한 이유는 서버 컴포넌트의 도입 때문인데요 하나의 페이지에서 컴포넌트가 각각 자신이 필요한 데이터를 페칭하기 때문에 한 페이지에서 서로 다른 컴포넌트에서 동일한 API를 요청하는 경우가 발생할 수 있기 때문입니다.

그리고 리퀘스트 메모이제이션은 Next.js에서 자동으로 제공하는 기능으로 별도의 설정은 필요하지 않습니다!


📚 참고
한 입 크기로 잘라먹는 next.js

profile
개발댕발

0개의 댓글