コンテンツにスキップ
DocsErrors`<Suspense>` の外でキャッシュされていないデータにアクセスしました

`<Suspense>` の外でキャッシュされていないデータにアクセスしました

このエラーが発生した理由

cacheComponents 機能が有効になっている場合、Next.js は、すべてのユーザーリクエストでアクセスされるべきデータを待機するコンポーネントの周囲に親 Suspense 境界があることを期待します。この要件の目的は、Next.js がこのデータへのアクセスとレンダリング中に役立つフォールバックを提供できるようにすることです。

リクエストヘッダーのように、ユーザーリクエストが処理されている場合にのみ本質的に利用可能ないくつかのデータがありますが、Next.js は、"use cache" を使用して明示的にキャッシュしない限り、デフォルトで非同期データはユーザーリクエストごとにアクセスされることを期待すると仮定します。

この特定のエラーの適切な修正は、アクセスしているデータと、Next.js アプリをどのように動作させたいかによって異なります。

修正方法

データのアクセス

fetch、データベースクライアント、または非同期 IO を行うその他のモジュールを使用してデータにアクセスすると、Next.js は、すべてのユーザーリクエストでデータがロードされることを期待するという意図を解釈します。

このデータがページを完全にまたは部分的に事前レンダリングしながら使用されることを期待している場合は、"use cache" を使用してキャッシュする必要があります。

変更前

app/page.js
async function getRecentArticles() {
  return db.query(...)
}
 
export default async function Page() {
  const articles = await getRecentArticles(token);
  return <ArticleList articles={articles}>
}

変更後

app/page.js
import { cacheTag, cacheLife } from 'next/cache'
 
async function getRecentArticles() {
  "use cache"
  // This cache can be revalidated by webhook or server action
  // when you call revalidateTag("articles")
  cacheTag("articles")
  // This cache will revalidate after an hour even if no explicit
  // revalidate instruction was received
  cacheLife('hours')
  return db.query(...)
}
 
export default async function Page() {
  const articles = await getRecentArticles(token);
  return <ArticleList articles={articles}>
}

このデータがすべてのユーザーリクエストでアクセスされるべき場合は、React の Suspense を使用してフォールバック UI を提供する必要があります。この Suspense 境界をアプリケーションのどこに配置するかは、レンダリングしたいフォールバック UI の種類によって決まります。これは、このデータにアクセスするコンポーネントのすぐ上、またはルートレイアウトに配置することもできます。

変更前

app/page.js
async function getLatestTransactions() {
  return db.query(...)
}
 
export default async function Page() {
  const transactions = await getLatestTransactions(token);
  return <TransactionList transactions={transactions}>
}

変更後

app/page.js
import { Suspense } from 'react'
 
async function TransactionList() {
  const transactions = await db.query(...)
  return ...
}
 
function TransactionSkeleton() {
  return <ul>...</ul>
}
 
export default async function Page() {
  return (
    <Suspense fallback={<TransactionSkeleton />}>
      <TransactionList/>
    </Suspense>
  )
}

ヘッダー

headers()cookies()、または draftMode() を使用してリクエストヘッダーにアクセスしている場合。これらの API の使用を既存のコンポーネントツリーのより深い位置に移動できるかどうかを検討してください。

変更前

app/inbox.js
export async function Inbox({ token }) {
  const email = await getEmail(token)
  return (
    <ul>
      {email.map((e) => (
        <EmailRow key={e.id} />
      ))}
    </ul>
  )
}
app/page.js
import { cookies } from 'next/headers'
 
import { Inbox } from './inbox'
 
export default async function Page() {
  const token = (await cookies()).get('token')
  return (
    <Suspense fallback="loading your inbox...">
      <Inbox token={token}>
    </Suspense>
  )
}

変更後

app/inbox.js
import { cookies } from 'next/headers'
 
export async function Inbox() {
  const token = (await cookies()).get('token')
  const email = await getEmail(token)
  return (
    <ul>
      {email.map((e) => (
        <EmailRow key={e.id} />
      ))}
    </ul>
  )
}
app/page.js
import { Inbox } from './inbox'
 
export default async function Page() {
  return (
    <Suspense fallback="loading your inbox...">
      <Inbox>
    </Suspense>
  )
}

または、リクエストヘッダーにアクセスしているコンポーネントの上に Suspense 境界を追加することもできます。

Params および SearchParams

Layout の params、および Page の params および searchParams プロパティは Promise です。Layout または Page コンポーネントでこれらを await すると、実際に必要とされるよりも高いレベルでこれらのプロパティにアクセスしている可能性があります。これらのプロパティを Promise としてより深いコンポーネントに渡し、実際の param または searchParam が必要とされる場所の近くで await することを試してください。

変更前

app/map.js
export async function Map({ lat, lng }) {
  const mapData = await fetch(`https://...?lat=${lat}&lng=${lng}`)
  return drawMap(mapData)
}
app/page.js
import { cookies } from 'next/headers'
 
import { Map } from './map'
 
export default async function Page({ searchParams }) {
  const { lat, lng } = await searchParams;
  return (
    <Suspense fallback="loading your inbox...">
      <Map lat={lat} lng={lng}>
    </Suspense>
  )
}

変更後

app/map.js
export async function Map({ coords }) {
  const { lat, lng } = await coords
  const mapData = await fetch(`https://...?lat=${lat}&lng=${lng}`)
  return drawMap(mapData)
}
app/page.js
import { cookies } from 'next/headers'
 
import { Map } from './map'
 
export default async function Page({ searchParams }) {
  const coords = searchParams.then(sp => ({ lat: sp.lat, lng: sp.lng }))
  return (
    <Suspense fallback="loading your inbox...">
      <Map coord={coords}>
    </Suspense>
  )
}

または、params または searchParams にアクセスしているコンポーネントの上に Suspense 境界を追加することもできます。これにより、Next.js は、このリクエストデータのアクセス中にどの UI を使用すればよいかを理解できるようになります。

一時的なキャッシュ

"use cache" は、実際には実用的には短すぎる cacheLife() を記述できるようにします。この方法の利点は、クライアントルーターキャッシュがブラウザでキャッシュエントリを再利用するためにゼロ以外のキャッシュ時間を記述できること、および高トラフィック時にアップストリーム API を保護するのに役立つことです。

"use cache" エントリが事前レンダリングされることを期待していた場合は、わずかに長い cacheLife() を記述してみてください。

変更前

app/page.js
import { cacheLife } from 'next/cache'
 
async function getDashboard() {
  "use cache"
  // This cache will revalidate after 1 second. It is so short
  // Next.js won't prerender it on the server but the client router
  // can reuse the result for up to 30 seconds unless the user manually refreshes
  cacheLife('seconds')
  return db.query(...)
}
 
export default async function Page() {
  const data = await getDashboard(token);
  return <Dashboard data={data}>
}

変更後

app/page.js
import { cacheLife } from 'next/cache'
 
async function getDashboard() {
  "use cache"
  // This cache will revalidate after 1 minute. It's long enough that
  // Next.js will still produce a fully or partially prerendered page
  cacheLife('minutes')
  return db.query(...)
}
 
export default async function Page() {
  const data = await getDashboard(token);
  return <Dashboard data={data}>
}

または、この一時的なキャッシュデータにアクセスしているコンポーネントの上に Suspense 境界を追加することもできます。これにより、Next.js は、ユーザーリクエストでこのデータにアクセス中にどの UI を使用すればよいかを理解できるようになります。