获取数据
¥Fetching Data
本页面将引导你了解如何在 服务器和客户端组件 中获取数据,以及如何对依赖于数据的组件进行 stream 操作。
¥This page will walk you through how you can fetch data in Server and Client Components, and how to stream components that depend on data.
获取数据
¥Fetching data
服务器组件
¥Server Components
你可以使用以下方式在服务器组件中获取数据:
¥You can fetch data in Server Components using:
使用 fetch
API
¥With the fetch
API
要使用 fetch
API 获取数据,请将你的组件变成异步函数,然后等待 fetch
调用。例如:
¥To fetch data with the fetch
API, turn your component into an asynchronous function, and await the fetch
call. For example:
export default async function Page() {
const data = await fetch('https://api.vercel.app/blog')
const posts = await data.json()
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
需要了解:
¥Good to know:
默认情况下,
fetch
响应不缓存。但是,Next.js 会将路由 prerender 化,并且输出将被缓存以提高性能。如果你想选择启用 动态渲染,请使用{ cache: 'no-store' }
选项。查看fetch
API 参考。¥
fetch
responses are not cached by default. However, Next.js will prerender the route and the output will be cached for improved performance. If you'd like to opt into dynamic rendering, use the{ cache: 'no-store' }
option. See thefetch
API Reference.在开发过程中,你可以记录
fetch
调用,以便更好地查看和调试。查看logging
API 参考。¥During development, you can log
fetch
calls for better visibility and debugging. See thelogging
API reference.
使用 ORM 或数据库
¥With an ORM or database
由于服务器组件在服务器上渲染,因此你可以安全地使用 ORM 或数据库客户端进行数据库查询。将你的组件转换为异步函数,并等待调用:
¥Since Server Components are rendered on the server, you can safely make database queries using an ORM or database client. Turn your component into an asynchronous function, and await the call:
import { db, posts } from '@/lib/db'
export default async function Page() {
const allPosts = await db.select().from(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
客户端组件
¥Client Components
有两种方法可以在客户端组件中获取数据,使用:
¥There are two ways to fetch data in Client Components, using:
使用 use
hook 进行流式数据处理
¥Streaming data with the use
hook
你可以使用 React 的 use
钩 到 stream 数据从服务器到客户端。首先从服务器组件中获取数据,然后将 promise 作为 prop 传递给客户端组件:
¥You can use React's use
hook to stream data from the server to client. Start by fetching data in your Server component, and pass the promise to your Client Component as prop:
import Posts from '@/app/ui/posts
import { Suspense } from 'react'
export default function Page() {
// Don't await the data fetching function
const posts = getPosts()
return (
<Suspense fallback={<div>Loading...</div>}>
<Posts posts={posts} />
</Suspense>
)
}
然后,在你的客户端组件中,使用 use
钩子读取 promise:
¥Then, in your Client Component, use the use
hook to read the promise:
'use client'
import { use } from 'react'
export default function Posts({
posts,
}: {
posts: Promise<{ id: string; title: string }[]>
}) {
const allPosts = use(posts)
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
在上面的示例中,<Posts>
组件被包裹在 <Suspense>
边界 中。这意味着在解决 promise 时将显示回退。了解有关 streaming 的更多信息。
¥In the example above, the <Posts>
component is wrapped in a <Suspense>
boundary. This means the fallback will be shown while the promise is being resolved. Learn more about streaming.
社区库
¥Community libraries
你可以使用社区库(如 SWR 或 React 查询)在客户端组件中获取数据。这些库具有自己的缓存、流式传输和其他功能的语义。例如,使用 SWR:
¥You can use a community library like SWR or React Query to fetch data in Client Components. These libraries have their own semantics for caching, streaming, and other features. For example, with SWR:
'use client'
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((r) => r.json())
export default function BlogPage() {
const { data, error, isLoading } = useSWR(
'https://api.vercel.app/blog',
fetcher
)
if (isLoading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<ul>
{data.map((post: { id: string; title: string }) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
流式
¥Streaming
警告:以下内容假定你的应用中已启用
dynamicIO
配置选项。该标志是在 Next.js 15 canary 中引入的。¥Warning: The content below assumes the
dynamicIO
config option is enabled in your application. The flag was introduced in Next.js 15 canary.
在服务器组件中使用 async/await
时,Next.js 将启用 动态渲染。这意味着将为每个用户请求获取数据并在服务器上渲染。如果有任何缓慢的数据请求,整个路由将被阻止渲染。
¥When using async/await
in Server Components, Next.js will opt into dynamic rendering. This means the data will be fetched and rendered on the server for every user request. If there are any slow data requests, the whole route will be blocked from rendering.
为了改善初始加载时间和用户体验,你可以使用流式传输将页面的 HTML 分解为更小的块,并逐步将这些块从服务器发送到客户端。
¥To improve the initial load time and user experience, you can use streaming to break up the page's HTML into smaller chunks and progressively send those chunks from the server to the client.

有两种方法可以在应用中实现流式传输:
¥There are two ways you can implement streaming in your application:
使用
loading.js
file 封装页面¥Wrapping a page with a
loading.js
file使用
<Suspense>
封装组件¥Wrapping a component with
<Suspense>
使用 loading.js
¥With loading.js
你可以在与页面相同的文件夹中创建一个 loading.js
文件,以便在获取数据时流式传输整个页面。例如,要流式传输 app/blog/page.js
,请在 app/blog
文件夹中添加文件。
¥You can create a loading.js
file in the same folder as your page to stream the entire page while the data is being fetched. For example, to stream app/blog/page.js
, add the file inside the app/blog
folder.

export default function Loading() {
// Define the Loading UI here
return <div>Loading...</div>
}
在导航时,用户将在页面渲染时立即看到布局和 加载状态。渲染完成后,新内容将自动交换。
¥On navigation, the user will immediately see the layout and a loading state while the page is being rendered. The new content will then be automatically swapped in once rendering is complete.

在幕后,loading.js
将嵌套在 layout.js
内,并会自动将 page.js
文件及其下方的任何子文件封装在 <Suspense>
边界内。
¥Behind-the-scenes, loading.js
will be nested inside layout.js
, and will automatically wrap the page.js
file and any children below in a <Suspense>
boundary.

这种方法适用于路由段(布局和页面),但对于更细粒度的流式传输,你可以使用 <Suspense>
。
¥This approach works well for route segments (layouts and pages), but for more granular streaming, you can use <Suspense>
.
使用 <Suspense>
¥With <Suspense>
<Suspense>
允许你更详细地了解要流式传输的页面部分。例如,你可以立即显示任何超出 <Suspense>
边界的页面内容,并在边界内的博客文章列表中流式传输。
¥<Suspense>
allows you to be more granular about what parts of the page to stream. For example, you can immediately show any page content that falls outside of the <Suspense>
boundary, and stream in the list of blog posts inside the boundary.
import { Suspense } from 'react'
import BlogList from '@/components/BlogList'
import BlogListSkeleton from '@/components/BlogListSkeleton'
export default function BlogPage() {
return (
<div>
{/* This content will be sent to the client immediately */}
<header>
<h1>Welcome to the Blog</h1>
<p>Read the latest posts below.</p>
</header>
<main>
{/* Any content wrapped in a <Suspense> boundary will be streamed */}
<Suspense fallback={<BlogListSkeleton />}>
<BlogList />
</Suspense>
</main>
</div>
)
}
创建有意义的加载状态
¥Creating meaningful loading states
即时加载状态是导航后立即向用户显示的后备 UI。为了获得最佳用户体验,我们建议设计有意义的加载状态,帮助用户了解应用正在响应。例如,你可以使用骨架和旋转器,或者未来屏幕中很小但有意义的部分,例如封面照片、标题等。
¥An instant loading state is fallback UI that is shown immediately to the user after navigation. For the best user experience, we recommend designing loading states that are meaningful and help users understand the app is responding. For example, you can use skeletons and spinners, or a small but meaningful part of future screens such as a cover photo, title, etc.
在开发过程中,你可以使用 React Devtools 预览和检查组件的加载状态。
¥In development, you can preview and inspect the loading state of your components using the React Devtools.
示例
¥Examples
顺序数据获取
¥Sequential data fetching
当树中的嵌套组件各自获取数据且请求不是 deduplicated 时,就会发生顺序数据获取,这会导致更长的响应时间。
¥Sequential data fetching happens when nested components in a tree each fetch their own data and the requests are not deduplicated, leading to longer response times.

在某些情况下,你可能需要这种模式,因为一个提取取决于另一个提取的结果。
¥There may be cases where you want this pattern because one fetch depends on the result of the other.
例如,只有当 <Artist>
组件完成获取数据后,<Playlists>
组件才会开始获取数据,因为 <Playlists>
依赖于 artistID
属性:
¥For example, the <Playlists>
component will only start fetching data once the <Artist>
component has finished fetching data because <Playlists>
depends on the artistID
prop:
export default async function Page({
params,
}: {
params: Promise<{ username: string }>
}) {
const { username } = await params
// Get artist information
const artist = await getArtist(username)
return (
<>
<h1>{artist.name}</h1>
{/* Show fallback UI while the Playlists component is loading */}
<Suspense fallback={<div>Loading...</div>}>
{/* Pass the artist ID to the Playlists component */}
<Playlists artistID={artist.id} />
</Suspense>
</>
)
}
async function Playlists({ artistID }: { artistID: string }) {
// Use the artist ID to fetch playlists
const playlists = await getArtistPlaylists(artistID)
return (
<ul>
{playlists.map((playlist) => (
<li key={playlist.id}>{playlist.name}</li>
))}
</ul>
)
}
为了提升用户体验,你应该使用 React <Suspense>
在数据获取时显示 fallback
。这将启用 streaming 并防止整个路由被连续的数据请求阻塞。
¥To improve the user experience, you should use React <Suspense>
to show a fallback
while data is being fetch. This will enable streaming and prevent the whole route from being blocked by the sequential data requests.
并行数据获取
¥Parallel data fetching
当路由中的数据请求被立即发起并同时开始时,就会发生并行数据获取。
¥Parallel data fetching happens when data requests in a route are eagerly initiated and start at the same time.
默认情况下,布局和页面 是并行渲染的。因此每个段都会尽快开始获取数据。
¥By default, layouts and pages are rendered in parallel. So each segment starts fetching data as soon as possible.
但是,在任何组件中,如果多个 async
/await
请求依次放置,仍然可以是连续的。例如,getAlbums
将被阻塞,直到 getArtist
被解析:
¥However, within any component, multiple async
/await
requests can still be sequential if placed after the other. For example, getAlbums
will be blocked until getArtist
is resolved:
import { getArtist, getAlbums } from '@/app/lib/data'
export default async function Page({ params }) {
// These requests will be sequential
const { username } = await params
const artist = await getArtist(username)
const albums = await getAlbums(username)
return <div>{artist.name}</div>
}
你可以通过在使用数据的组件外部定义请求并将它们一起解析来并行发起请求,例如使用 Promise.all
:
¥You can initiate requests in parallel by defining them outside the components that use the data, and resolving them together, for example, with Promise.all
:
import Albums from './albums'
async function getArtist(username: string) {
const res = await fetch(`https://api.example.com/artist/${username}`)
return res.json()
}
async function getAlbums(username: string) {
const res = await fetch(`https://api.example.com/artist/${username}/albums`)
return res.json()
}
export default async function Page({
params,
}: {
params: Promise<{ username: string }>
}) {
const { username } = await params
const artistData = getArtist(username)
const albumsData = getAlbums(username)
// Initiate both requests in parallel
const [artist, albums] = await Promise.all([artistData, albumsData])
return (
<>
<h1>{artist.name}</h1>
<Albums list={albums} />
</>
)
}
需要了解:如果使用
Promise.all
时一个请求失败,则整个操作将失败。要处理这种情况,你可以改用Promise.allSettled
方法。¥Good to know: If one request fails when using
Promise.all
, the entire operation will fail. To handle this, you can use thePromise.allSettled
method instead.
预加载数据
¥Preloading data
你可以通过创建一个实用程序函数来预加载数据,并在阻塞请求之前立即调用该函数。<Item>
根据 checkIsAvailable()
函数进行有条件渲染。
¥You can preload data by creating an utility function that you eagerly call above blocking requests. <Item>
conditionally renders based on the checkIsAvailable()
function.
你可以在 checkIsAvailable()
之前调用 preload()
以立即启动 <Item/>
数据依赖。在渲染 <Item/>
时,其数据已被获取。
¥You can call preload()
before checkIsAvailable()
to eagerly initiate <Item/>
data dependencies. By the time <Item/>
is rendered, its data has already been fetched.
import { getItem } from '@/lib/data'
export default async function Page({
params,
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params
// starting loading item data
preload(id)
// perform another asynchronous task
const isAvailable = await checkIsAvailable()
return isAvailable ? <Item id={id} /> : null
}
export const preload = (id: string) => {
// void evaluates the given expression and returns undefined
// https://web.nodejs.cn/docs/Web/JavaScript/Reference/Operators/void
void getItem(id)
}
export async function Item({ id }: { id: string }) {
const result = await getItem(id)
// ...
}
此外,你可以使用 React 的 cache
功能 和 server-only
包 创建可复用的实用函数。此方法允许你缓存数据获取函数,并确保它仅在服务器上执行。
¥Additionally, you can use React's cache
function and the server-only
package to create a reusable utility function. This approach allows you to cache the data fetching function and ensure that it's only executed on the server.
import { cache } from 'react'
import 'server-only'
import { getItem } from '@/lib/data'
export const preload = (id: string) => {
void getItem(id)
}
export const getItem = cache(async (id: string) => {
// ...
})