Programming/Next.js

[Next.js 13] 공식 문서 Data Fetching 1(Fundamentals ~ Caching)

리버김 2023. 2. 19.
나만의 홈페이지 만들기 프로젝트를 진행하면서 이제 data fetching을 해야 할 때가 왔다. 그런데 Next 13의 data fetching은 Next 12와 변경점이 굉장히 많았다.(12도 잘 모르는데) 관련 공식문서를 전부 번역해보면서 공부해보자.

 

https://beta.nextjs.org/docs/data-fetching/fundamentals

 

Data Fetching: Fundamentals | Next.js

Learn the fundamentals of data fetching with React and Next.js.

beta.nextjs.org

https://github.com/acdlite/rfcs/blob/first-class-promises/text/0000-first-class-support-for-promises.md

 

GitHub - acdlite/rfcs: RFCs for changes to React

RFCs for changes to React. Contribute to acdlite/rfcs development by creating an account on GitHub.

github.com

Fundamentals

Next.js 13은 당신의 어플리케이션에서 data를 fetch하는 새로운 방법을 소개한다.

API가 간소화됨으로써 React와 Web Platform과 함께 더 잘 융합된다. 이 문서는 당신의 data의 lifecycle을 관리하는 것을 돕기 위한 기본적인 개념과 패턴에 대해 설명한다.

 

Good to Know: getServerSideProps, getStaticProps 그리고 getInitialProps와 같은 이전의 Next.js의 data fetching 도구들은 새로운 app directory에서는 지원되지 않는다!!(엄청난 변화)

개요

이 문서에서의 권장 사항에 대한 개요다.

 

  1. Server Components를 사용하여 서버에서 data를 fetch하라.
  2. 로딩 시간을 줄이고 waterfalls를 최소화하기 위하여 data를 수평적으로 fetch하라.
  3. Layouts와 Pages에 관하여, data가 사용될 곳에서 fetch하라. Next.js가 트리 안에서의 요청에 대해서는 자동적으로 중복을 제공해 줄 것이다.
  4. 로딩 UI, Streaming 그리고 Suspense를 사용하라. 그로써 점진적으로 페이지를 렌더하고, 나머지 콘텐츠가 로드되는 동안 유저에게 결과를 보여줄 수 있다.

 

fetch() API

새로운 data fetching 시스템은 native fetch() Web API를 기반으로 만들어졌다. 또한, server components에서 async/await를 사용한다.

 

fetch()는 원격 자원을 fetch 하기 위한 Web API로, promise를 반환한다. React는 중복 요청을 자동으로 제거하는 기능을 제공하기 위해 fetch를 확장했으며, Next.js는 각 요청이 자체 캐싱 및 재검증을 설정할 수 있도록 fetch의 옵션 개체를 확장했다.

Fetching Data on the Server

가능한 모든 경우에서 우리는 Server Components에서 data를 fetch할 것을 추천한다. Server Components는 항상 server에서 data를 fetch한다. 이것은 당신에게 다음과 같은 것들을 가능케 해준다.

 

  • backend의 data 자원에 대한 직접적 엑세스를 갖게 해준다.
  • access token이나 API key와 같은 민감한 정보가 클라이언트에 노출되지 않도록 해주어 당신의 어플리케이션을 더 안전하게 만든다.
  • data fetching과 render를 같은 환경에서 한다. 이것은 클라이언트와 서버 간 왔다 갔다 하는 커뮤니케이션을 줄여주며, 클라이언트의 메인 스레드에서의 일도 줄여준다.
  • 클라이언트에서 여러 번의 개별 요청을 보내는 대신, 한 번의 왕복으로 복수의 data를 fetch한다.
  • client-server wateralls를 줄여준다.
  • 당신의 지역에 따라 데이터 가져오기가 데이터 원본에 더 가깝게 발생할 수도 있으므로 대기 시간이 줄어들고 성능이 향상된다.
Good to know: 여전히 client에서 data를 fetch할 수 있다. 이 경우 SWR이나 React Query와 같은 서드-파티 라이브러리를 사용하는 것을 권한다. 나중에는 React의 use() hook을 사용해 Client Components에서 data를 fetch할 수 있게 될 것이다.

 

Component-level Data Fetching

이 새로운 모델에서, 당신은 layouts, pages 그리고 components에서 data를 fetch할 수 있다. Data fetching은 Streaming과 Suspense와도 양립할 수 있다.

 

Good to know: Layouts에 관하여 부모 레이아웃과 자식 간 데이터 전송은 불가하다. 우리는 데이터를 필요로 하는 layout에서 직접적으로 data를 fetching할 것을 권장한다. 한 route 안에서 같은 데이터를 중복으로 요청하게 되더라도 말이다. React와 Next.js가 같은 데이터가 한 번이상 fetch되는 것을 막기 위해서 보이지 않는 곳에서 캐쉬와 중복 제거 작업을 할 것이다.

 

Parallel and Sequential Data Fetching

컴포넌트 안에서 data를 fetching할 때 여러분은 두 가지 data fetching 패턴에 대해 알고 있어야 한다: Parallel(평행한) 그리고 Sequential(순차적인)

 

 

  • parallel data fetching에서는 rotue에서의 요청이 즉시 시작되며 동시에 데이터를 로드할 것이다. 이것은 client-server waterfalls와 데이터를 로드하는 데 드는 시간을 줄여준다.
  • sequential data fetching에서는 route에서의 요청들이 각각 독립적이며 waterfall을을 만들어 낸다. 어떤 fetch가 다른 것에 의존적이라거나, 다음 fetch 전에 어떤 조건이 충족되도록 하여 자원을 절약하고 싶다면 이 패턴을 사용하고 싶을 수도 있다. 그러나, 이 동작은 의도적이지 않은 것이거나 더 긴 로딩 시간을 야기할 수도 있는 것이다.

 

Automatic fetch() Request Deduping

만약 여러분이 current user와 같이 트리 안에 있는 복수의 컴포넌트에서 같은 데이터를 fetch하고 싶다면, Next.js는 임시 캐시에 동일한 입력이 있는 fetch 요청(GET)을 자동으로 캐시할 것이다. 이것은 deduping이라고 부르는 최적화이며, rendering 과정에서 같은 데이터가 한 번 이상 fetch되는 것을 방지해 준다.

 

 

  • 서버에서 캐시는 렌더링 과정이 완료되기 전까지 서버 요청의 수명만큼 지속한다.
  • 클라이언트에서 캐시는 전체 페이지가 다시 로드되기 전까지 세션(복수의 client-side 리렌더링을 포함할 수 있는)의 지속 시간만큼 지속된다.
  • POST 요청은 자동적으로 중복 제거되지 않는다.

 

Static and Dynamic Data Fetches

데이터에는 두 종류가 있다: StaticDynamic

  • Static Data: 자주 바뀌지 않는 데이터다. 블로그 글 같은 것이 있다.
  • Dynamic Data: 유저에 따라 다르거나 자주 바뀌는 데이터다. 장바구니와 같은 것이 있다.

 

 

Next.js는 기본적으로 static fetch를 한다. 즉, 데이터는 빌드 시간에 가져와 캐시되고 각 요청에서 재사용된다는 의미다. 개발자로서 여러분은 정적 데이터가 캐시되고 재검증되는 방법을 제어할 수 있다.

 

정적 데이터를 사용하는 데는 두 가지 장점이 있다.

 

  1. 요청 수를 최소화하여 데이터베이스의 부하를 줄인다
  2. 로딩 성능 향상을 위해 데이터가 자동으로 캐시된다.

 

Caching Data

캐싱은 특정 위치(예: 콘텐츠 전송 네트워크)에 데이터를 저장하는 프로세스이므로 요청 시마다 원래 소스에서 다시 가져올 필요가 없다.

 

 

Next.js 캐시는 전역적으로 분배될 수 있는 영구 HTTP 캐시다. 이는 캐시가 자동으로 확장되고 플랫폼(예: Vercel)에 따라 여러 지역에서 공유될 수 있음을 의미한다.

 

Next.js는 서버의 각 요청이 자체 영구 캐싱 동작을 설정할 수 있도록 fetch() 함수의 옵션 개체를 확장했다. component-level에서의 data fetching과 함께, 이는 데이터가 사용되는 애플리케이션 코드 내에서 직접 캐싱을 구성할 수 있도록 해준다.

 

서버 렌더링 동안, Next.js가 fetch를 만나면, Next.js는 데이터가 이미 있는지 캐시를 확인한다. 만약 있다면 캐시되었던 데이터를 반환한다. 만약 없다면 fetch하고 추후 요청에 대비해 데이터를 저장할 것이다.

 

Good to know: 여러분이 fetch를 사용할 수 없는 상황에 대비해 React는 cache 함수를 제공한다. 이는 요청 시간동안 수동으로 데이터를 캐시할 수 있도록 해준다.

 

Revalidating Data

유효성 재검사는 캐시를 제거하고 최신 데이터를 re-fetching하는 과정이다. 이것은 여러분의 데이터가 변화하거나 다시 빌드하지 않고 여러분의 어플리케이션이 최신 버전의 데이터를 보여주는 것을 확인하고 싶을 때 유용하다.

 

Next.js는 두 가지의 유효성 재검사를 제공한다.

 

  • Background: 일정한 시간 간격에 따라 데이터의 유효성을 재검사한다.
  • On-demand: 업데이트가 있을 때마다 데이터의 유효성을 재검사한다.

 

Streaming and Suspense

treaming 및 Suspense는 UI의 렌더링된 단위를 클라이언트에 점진적으로 렌더링하고 증분적으로 스트리밍할 수 있는 새로운 React 기능이다.

 

Server Components와 nested layouts를 사용하면 특별히 데이터가 필요하지 않은 페이지 부분을 즉시 렌더링하고 데이터를 가져오고 있는 페이지 부분에 대한 loading 상태를 표시할 수 있다. 이것은 사용자가 전체 페이지가 로드될 때까지 기다리지 않아도 상호 작용을 시작할 수 있음을 의미한다.

 

 

Streaming과 Suspense에 대해 더 알고 싶다면 Loading UIStreaming and Suspense 페이지를 참고하자.

 

 

Fetching

Good to know: 이 새로운 data fetching 모델은 개발중이며 아직 안정되지 않았다. 또, 공부 전 위 RFC 문서를 읽는 것이 좋다. 위 문서는 서버 컴포넌트에서의 async/await과 클라이언트 컴포넌트에서의 새로운 use() hook을 소개하고 있다.

(위 Good to know를 읽고, use() hook에 대해서 잠시 찾아봤다. 또, async await은 왜 서버 컴포넌트에 국한되는지도 궁금했다.

 

위에 링크된 React의 RFC를 보면, Client Component에서 await가 지원되지 않는 이유는 기술적인 한계 때문이라고 한다. 구체적인 이유에 대해서는 아직은 잘 모르겠다.

 

그 대신 use라는 hook을 쓰면 되는데, use(promise)가 곧 await promise와 같은 표현이 된다.

다른 hook들과 달리 조건, 블럭, 반복문 안에 위치할 수 있다.)

 

React와 Next.js 13 는 데이터를 fetch하고 관리하는 새로운 방법을 소개한다. 새로은 data fetching 시스템은 app directory 안에서 작동하며, 'fetch()' Web API를 기반으로 구축되었다.

 

fetch()는 원격 자원을 fetch 하기 위한 Web API로, promise를 반환한다. React는 중복 요청을 자동으로 제거하는 기능을 제공하기 위해 fetch를 확장했으며, Next.js는 각 요청이 자체 캐싱 및 재검증을 설정할 수 있도록 fetch의 옵션 개체를 확장했다.

async/await in Server Components

위 RFC에 따르면, Server Component에서는 data를 fetch하기 위하여 async와 await를 사용할 수 있다.

 

// app/page.tsx

async function getData() {
  const res = await fetch('https://api.example.com/...');
  // The return value is *not* serialized
  // You can return Date, Map, Set, etc.

  // Recommendation: handle errors
  if (!res.ok) {
    // This will activate the closest `error.js` Error Boundary
    throw new Error('Failed to fetch data');
  }

  return res.json();
}

export default async function Page() {
  const data = await getData();

  return <main></main>;
}
경고: 당신은 async/await을 layouts와 pages에서 쓸 수 있다. 왜냐하면 Server Components기 때문이다. async/awiat을 TypeScript와 함께 다른 컴포넌트들에서 쓰는 것은 JSX로부터의 response type에서 오류를 야기할 수 있다. 우리는 TypeScript 팀과 함께 이 upstream을 해결하기 위해 노력하고 있다. 

임시방편으로써 당신은 {/* @ts-expect-error Server Component */} 을 사용해 컴포넌트에 대한 type 검사를 비활성화할 수 있다.

 

Server Component Functions

Next.js는 서버에서의 data fetching에서 유용하게 사용할 만한 서버 함수들을 제공한다.

 

 

use in Client Components

use는 await와 개념적으로 유사한, promise를 받아들이는 새로운 React 함수다. use는 컴포넌트, hooks, Suspense와 호환되는 방식으로 함수에 의해 반환되는 promise를 처리한다. 관련 내용을 React RFC에서 알아보자.

 

현재 use에서 사용되는 fetch 래핑은 Client Components에서 권장되지 않으며, 복수의 리렌더링을 촉발할 수 있다. 현재 여러분이 client component에서 data를 fetch할 필요가 있다면 SWR이나 React Query와 같은 서드-파티 라이브러리를 사용할 것을 권한다.

 

Note: 우리는 fetch와 use가 Client Components에서 사용할 수 있게 되면 더 많은 예시들을 추가할 것이다.

 

Static Data Fetching

기본적을 fetch는 자동으로 데이터를 fetch하고 무제한으로 캐싱한다.

 

fetch('https://...'); // cache: 'force-cache' is the default

 

Revalidating Data

캐시된 데이터를 시간 간격으로 유효성 재검사하려면 fetch()의 next.revalidate 옵션을 사용하여 리소스의 캐시 수명(초)을 설정할 수 있다.

 

fetch('https://...', { next: { revalidate: 10 } });

 

Dynamic Data Fetching

모든 fetch 요청에서 신선한 데이터를 fetch하려면, cache: 'no-stre' 옵션을 사용하라.

 

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

 

Data Fetching Patterns

Parallel Data Fetching

client-server waterfalls를 최소화하려면 데이터를 수평적으로 fetch하는 이러한 패턴을 권장한다.

 

// app/artist/[username]/page.tsx

import Albums from './albums';

async function getArtist(username) {
  const res = await fetch(`https://api.example.com/artist/${username}`);
  return res.json();
}

async function getArtistAlbums(username) {
  const res = await fetch(`https://api.example.com/artist/${username}/albums`);
  return res.json();
}


export default async function Page({ params: { username } }) {
  // Initiate both requests in parallel
  const artistData = getArtist(username);
  const albumsData = getArtistAlbums(username);

  // Wait for the promises to resolve
  const [artist, albums] = await Promise.all([artistData, albumsData]);

  return (
    <>
      <h1>{artist.name}</h1>
      <Albums list={albums}></Albums>
    </>
  );
}

 

Server Component에서 await을 호출하기 전에 fetch를 시작하면, 각 요청은 동시에 요청을 fetch하는 것을 즉시 시작한다. 이것은 컴포넌트를 설정하여 waterfalls를 피할 수 있도록 해준다.

 

우리는 두 요청을 동시에 시작하여 시간을 절약할 수 있다. 그러나 사용자는 두 promise들이 resolve되기 전에는 렌더링된 결과를 볼 수 없다.

 

사용자 경험을 개선하려면 여러분은 suspense 영역을 추가하여 렌더링 작업을 분할하고 결과의 일부를 가능한 빨리 표시할 수 있다.

 

// artist/[username]/page.jsx

// ...

export default async function Page({ params: { username } }) {
  // Initiate both requests in parallel
  const artistData = getArtist(username);
  const albumData = getArtistAlbums(username);

  // Wait for the artist's promise to resolve first
  const artist = await artistData;

  return (
    <>
      <h1>{artist.name}</h1>
      {/* Send the artist information first,
      and wrap albums in a suspense boundary */}
      <Suspense fallback={<div>Loading...</div>}>
        <Albums promise={albumData} />
      </Suspense>
    </>
  );
}

// Albums Component
async function Albums({ promise }) {
  // Wait for the albums promise to resolve
  const albums = await promise;

  return (
    <ul>
      {albums.map((album) => (
        <li key={album.id}>{album.name}</li>
      ))}
    </ul>
  );
}

컴포넌트 구조를 개선하는 법에 대한 추가적인 정보를 preloading pattern에서 볼 수 있다.

 

Sequential Data Fetching

데이터를 순차적으로 fetch하려면 여러분은 데이터를 필요로하는 컴포넌트에서 직접적으로 fetch하거나 데이터를 필요로 하는 컴포넌트 안에서 fetch한 결과에 await 처리를 할 수 있다.

 

// app/artist/page.tsx

// ...

async function Playlists({ artistID }) {
  // Wait for the playlists
  const playlists = await getArtistPlaylists(artistID);

  return (
    <ul>
      {playlists.map((playlist) => (
        <li key={playlist.id}>{playlist.name}</li>
      ))}
    </ul>
  );
}

export default async function Page({ params: { username } }) {
  // Wait for the artist
  const artist = await getArtist(username);

  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <Playlists artistID={artist.id} />
      </Suspense>
    </>
  );
}

컴포넌트 안에서 data를 fetch함으로써 각 fetch 요청과 route의 nested segment들은 이전 요청 혹은 segment가 완료되기 전까지 data fetching을 시작할 수 없다.

 

Blocking Rendering in a Route

layout 안에서 data를 fetch함을 통해서 그 안에서 route segment들을 렌더링하는 것은 데이터가 로딩을 마친 후에 시작할 수 있다.

 

pages directory에서는 server-rendering을 사용하는 pages의 경우 getServerSideProps가 완료되기 전까지 로딩 스피너를 보여줬다. 그리고 해당 페이지를 위한 React component를 보여줬다. 이것은 "All or Nothing" data fetching으로 설명될 수 있다.

 

app directory의 경우 추가적인 옵션들이 있다.

 

  1. 첫째로, 여러분은 data fetching 함수의 결과를 스트리밍하는 동안  서버의 로딩 상태를 보여줄 수 있는 loading.js를 사용할 수 있다.
  2. 둘째로, 여러분은 렌더링 블라킹을 필요한 페이지에만 적용하기 위하여 data fetching을 컴포넌트 트리의 더 낮은 곳으로 옮길 수 있다. 예를 들어 root layout에서 데이터를 fetch하기 보다 특정 컴포넌트로 옮기는 것이다.

 

가능하면 항상 데이터를 사용하는 segment에서 fetch하라. 이것은 여러분으로 하여금 로딩이 되는 페이지에서만 로딩 상태를 보여주는 것을 가능하게 한다.

 

Data Fetching without fetch()

만약 여러분이 ORM이나 DB client와 같은 서드-파티 라이브러리를 사용하고 있다면 항상 fetch 요청을 사용할 수는 없을 거다.

 

만약 fetch를 쓸 수 없는데 layout이나 page에서의 caching이나 revalidating을 제어하고 싶다면 default caching behavior에 의존하거나 segment chache configuration을 사용하면 된다.

 

Default Caching Behavior

fetch 를 직접적으로 사용하지 않는 어떤 data fetching library도 route에서의 캐싱에 영향을 미치지 않을 것이며, route segment에 따라 static하거나 dynamic할 것이다.

 

segment가 static(default)하다면, 요청의 결과는 캐시될 것이며 segment의 나머지 부분과 함께 유효성 재검증될 것이다. segment가 dynamic한 경우 요청의 결과는 캐시되지 않으며, segment가 렌더링될 때 모든 요청을 re-fetch할 것이다.

 

Good to know: cookies()와 headers()와 같은 Dynamic functions들은 route segment를 dynamic하기 만들 것이다.

 

Segment Cache Configuration

임시 솔루션으로 타사 쿼리의 캐싱 동작을 구성할 수 있을 때까지 세그먼트 구성을 사용하여 전체 세그먼트의 캐시 동작을 사용자 지정할 수 있다.

// app/page.tsx

import prisma from './lib/prisma';

export const revalidate = 3600; // revalidate every hour

async function getPosts() {
  const posts = await prisma.post.findMany();
  return posts;
}

export default async function Page() {
  const posts = await getPosts();
  // ...
}

 

Caching

Next.js에는 캐싱하는 두 가지 방법이 있다: Segment-level과 Per-Request Cache

Segment-level Caching

Segment-level caching은 route segments에 사용되는 데이터들을 재검증하고 캐싱할 수 있도록 해준다.

이 메커니즘을 사용하면 경로의 여러 다른 segment들이 route의 캐시 생명주기를 관리할 수 있다.

route 위계의 각 page.tsx와 layout.tsx는 route의 재검증 주기를 설정하는 revalidate라는 값을 export할 수 있다.

// app/page.tsx

export const revalidate = 60;  // revalidate this page every 60 seconds

 

export된 revalidate 값에 더하여, fetch를 사용해 만든 request들은 route의 revalidate 빈도를 제어하는 revalidate 옵션을 지정할 수 있다.

// app/page.tsx

// revalidate this page every 10 seconds, since the getData's fetch
// request has `revalidate: 10`.
async function getData() {
  const res = await fetch('https://...', { next: { revalidate: 10 } });
  return res.json();
}

export default async function Page() {
  const data = await getData();
  // ...
}

 

page, layout 그리고 fetch 요청은 모두 revalidation 빈도를 지정할 수 있으며, 그 중 가장 낮은 값이 사용될 것이다.

 

revalidate 옵션들

https://beta.nextjs.org/docs/api-reference/segment-config#revalidate

 

Segment Config Options | Next.js

Learn about how to configure options for Next.js route segments.

beta.nextjs.org

 

Good to know:

  • fetchCache를 'only-cache' 또는 'force-cache'로 설정하여 모든 fetch 요청이 캐싱되게 할 수 있지만, 개별 fetch 요청에 의해 재검증 빈도가 계속 낮아질 수 있다.

 

Per-request Caching

Per-request caching은 각 요청마바다 요청을 캐싱하고 중복 제거할 수 있도록 해준다.

React에 cache()라는 새로운 함수가 있다. 이것은 감싸진 함수의 결과를 메모해준다. 동일한 arguments를 사용하는 동일한 함수는 새로 실행되는 대신 캐시된 값을 재사용할 것이다. cache() 함수는 각 per-request basis의 data fetch를 중복제거하는 데 사용할 수 있다. 만약 동일한 arguments를 가진 함수 인스턴스가 서버 요청 내에서 이전에 호출된 적이 있다면, 우리는 캐시된 값을 return할 수 있다.

 

// utils/getUser.ts

import { cache } from 'react';

export const getUser = cache(async (id: string) => {
  const user = await db.user.findUnique({ id });
  return user;
});

// app/user/[id]/layout.tsx

import { getUser } from '@utils/getUser';

export default async function UserLayout({ params: { id } }) {
  const user = await getUser(id);
  // ...
}

// app/user/[id]/page.tsx

import { getUser } from '@utils/getUser';

export default async function UserLayout({
  params: { id },
}: {
  params: { id: string };
}) {
  const user = await getUser(id);
  // ...
}

위 코드에서 getUser() 함수는 두 번 호출되었지만, DB에는 한 번의 query만이 만들어질 것이다. 왜냐하면 getUser()함수가 cache()로 감싸져 있어서 두 번째 요청은 첫 번째 요청의 결과를 재사용할 것이기 때문이다.

 

Good to know:

  • fetch()는 cache()를 자동적으로 지원하도록 이미 패치됐다. 그러니 fetch()를 cache()로 감싸지 않아도 된다. (위에 번역한 'Automatic fetch() Request Deduping' 부분을 보면 됨 ㅎㅎ)
  • 이 새로운 모델에서 우리는 prop을 내려보내는 것보다 데이터가 필요한 컴포넌트 내부에서 직접 fetch하는 것을 권한다.
  • 여러분이 컨트롤하지 않는서드파티 라이브러리도 cache로 감싸라.
  • cache를 사용하면 부모 레이아웃이 요청 중 렌더링되는지 여부를 알 필요 없이 data fetching을 공유할 수 있다(?)
  • 서버 data fetching 함수가 실수로 클라이언트에서 사용되지 않도록 하기 위해 server-only package를 사용할 것을 권한다.

 

GraphQL and cache()

POST 요청은 fetch를 사용할 때 자동으로 중복 제거되지 않는다. 오직 GET 요청만 그렇다. 만약 여러분이 GraphQL과 POST 요청을 사용하고 있다면, 여러분은 cache를 사용해 요쳥을 중복 제거할 수 있다. cache arguments는 flat하며 primitives만 포함해야 한다. Deep objects들은 중복 제거에 알맞지 않다.

// utils/getUser.ts

import { cache } from 'react';

export const getUser = cache(async (id: string) => {
  const res = await fetch('/graphql', { method: 'POST', body: '...' })
  // ...
});

 

cache()와 함께 하는 preload 패턴

prelaod란? 현재 페이지에서 사용할 리소스에 우선순위를 높게 해 빠르게 가져오게 하는 것이다. 웹 폰트, 비즈니스적으로 필요한 이미지 같이 빠르게 로드되어야 하는 곳에서 사용한다.

우리는 preload() 패턴을 선택적으로 사용할 것을 권한다.

// components/User.tsx

import { getUser } from "@utils/getUser";

export const preload = (id: string) => {
  // void evaluates the given expression and returns undefined
  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/void
  void getUser(id);
}
export default async function User({ id }: { id: string }) {
  const result = await getUser(id);
  // ...
}

 

preload를 호출함으로써 필요할 가능성이 있는 데이터를 fetching하는 것을 eagerly 시작할 수 있다.

// app/user/[id]/page.tsx

import User, { preload } from '@components/User';

export default async function Page({
  params: { id },
}: {
  params: { id: string };
}) {
  preload(id); // starting loading the user data now
  const condition = await fetchCondition();
  return condition ? <User id={id} /> : null;
}

Good to know:

  • preload() 함수는 어떤 이름이어도 상관 없다. API가 아닌 패턴에 지나지 않기 떄문이다.
  • 이 패턴은 완전히 선택적인 것이고, 경우에 따라 최적화할 수 있는 도구다. 이 패턴은 parallel data fetching 위에서 발전된 최적화 도구다. 이제 여러분은 Promise를 props로 전달할 필요 없이 preload 패턴을 이용할 수 있다(?)

 

Combining cache, preload, and server-only

여러분은 cache 함수, preload 패턴, 그리고 server-only package를 결합해 앱 전체적으로 사용될 수 있는 data fetching utility를 만들 수 있다.

// utils/getUser.ts

import { cache } from 'react';
import 'server-only';

export const preload = (id: string) => {
  void getUser(id);
}

export const getUser = cache(async (id: string) => {
  // ...
});

 

이러한 접근을 통해 data를 eagerly fetch할 수 있으며, 응답을 캐싱하고, 이 data fetching이 오직 서버에서만 일어남을 보장할 수 있다. 

 

getUser.ts가 export하는 것들은 레이아웃, 페이지, 컴포넌트에서 사용하여 사용자 데이터를 가져오는 시기를 제어할 수 있다.

 

댓글