未缓存数据在 `<Suspense>` 之外被访问
此错误发生的原因
当启用 cacheComponents 功能时,Next.js 要求任何等待需要每次用户请求时访问的数据的组件,其外部有一个父级 Suspense 边界。此要求旨在让 Next.js 在访问和渲染这些数据时提供有用的回退。
虽然某些数据本质上仅在处理用户请求时可用,例如请求头,但 Next.js 默认假定,除非您使用 "use cache" 明确缓存它,否则任何异步数据都应在每次处理用户请求时访问。
此特定错误的正确修复取决于您正在访问的数据以及您希望 Next.js 应用程序的行为方式。
可能的解决方法
访问数据
当您使用 fetch、数据库客户端或任何其他执行异步 IO 的模块访问数据时,Next.js 会将您的意图解释为期望数据在每次用户请求时加载。
如果您期望在完全或部分预渲染页面时使用此数据,则必须使用 "use cache" 进行缓存。
之前
async function getRecentArticles() {
return db.query(...)
}
export default async function Page() {
const articles = await getRecentArticles(token);
return <ArticleList articles={articles}>
}之后
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 类型来决定。它可以紧接在访问此数据的组件上方,甚至在您的根布局中。
之前
async function getLatestTransactions() {
return db.query(...)
}
export default async function Page() {
const transactions = await getLatestTransactions(token);
return <TransactionList transactions={transactions}>
}之后
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 的使用移到现有组件树的更深层。
之前
export async function Inbox({ token }) {
const email = await getEmail(token)
return (
<ul>
{email.map((e) => (
<EmailRow key={e.id} />
))}
</ul>
)
}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>
)
}之后
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>
)
}import { Inbox } from './inbox'
export default async function Page() {
return (
<Suspense fallback="loading your inbox...">
<Inbox>
</Suspense>
)
}或者,您可以在访问请求头的组件上方添加一个 Suspense 边界。
参数和搜索参数
布局 params 以及页面 params 和 searchParams 属性都是 Promise。如果您在布局或页面组件中等待它们,您可能在实际需要这些属性的更高层级访问它们。尝试将这些属性作为 Promise 传递给更深层的组件,并在实际需要参数或搜索参数的位置附近等待它们。
之前
export async function Map({ lat, lng }) {
const mapData = await fetch(`https://...?lat=${lat}&lng=${lng}`)
return drawMap(mapData)
}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>
)
}之后
export async function Map({ coords }) {
const { lat, lng } = await coords
const mapData = await fetch(`https://...?lat=${lat}&lng=${lng}`)
return drawMap(mapData)
}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()。
之前
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}>
}之后
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。
有用链接
这有帮助吗?