App Router 增量采用指南
本指南将帮助你:
¥This guide will help you:
-
¥Update your Next.js application from version 12 to version 13
-
¥Upgrade features that work in both the
pages
and theapp
directories -
¥Incrementally migrate your existing application from
pages
toapp
升级
¥Upgrading
Node.js 版本
¥Node.js Version
Node.js 的最低版本现在是 v18.17。请参阅 Node.js 文档 了解更多信息。
¥The minimum Node.js version is now v18.17. See the Node.js documentation for more information.
Next.js 版本
¥Next.js Version
要更新到 Next.js 版本 13,请使用你首选的包管理器运行以下命令:
¥To update to Next.js version 13, run the following command using your preferred package manager:
npm install next@latest react@latest react-dom@latest
ESLint 版本
¥ESLint Version
如果你使用 ESLint,则需要升级 ESLint 版本:
¥If you're using ESLint, you need to upgrade your ESLint version:
npm install -D eslint-config-next@latest
很高兴知道:你可能需要在 VS Code 中重新启动 ESLint 服务器以使 ESLint 更改生效。打开命令面板(Mac 上为
cmd+shift+p
;Windows 上为ctrl+shift+p
)并搜索ESLint: Restart ESLint Server
。¥Good to know: You may need to restart the ESLint server in VS Code for the ESLint changes to take effect. Open the Command Palette (
cmd+shift+p
on Mac;ctrl+shift+p
on Windows) and search forESLint: Restart ESLint Server
.
下一步
¥Next Steps
更新后,请参阅以下部分了解后续步骤:
¥After you've updated, see the following sections for next steps:
-
升级新功能:帮助你升级到新功能(例如改进的图片和链接组件)的指南。
¥Upgrade new features: A guide to help you upgrade to new features such as the improved Image and Link Components.
-
从
pages
目录迁移到app
目录:帮助你从pages
目录逐步迁移到app
目录的分步指南。¥Migrate from the
pages
toapp
directory: A step-by-step guide to help you incrementally migrate from thepages
to theapp
directory.
升级新功能
¥Upgrading New Features
Next.js 13 引入了具有新功能和约定的新 应用路由。新路由位于 app
目录中,并与 pages
目录共存。
¥Next.js 13 introduced the new App Router with new features and conventions. The new Router is available in the app
directory and co-exists with the pages
directory.
升级到 Next.js 13 不需要使用新的 应用路由。你可以继续使用 pages
以及在两个目录中均有效的新功能,例如更新的 图片组件、链接组件、脚本组件 和 字体优化。
¥Upgrading to Next.js 13 does not require using the new App Router. You can continue using pages
with new features that work in both directories, such as the updated Image component, Link component, Script component, and Font optimization.
<Image/>
组件
¥<Image/>
Component
Next.js 12 通过临时导入对图片组件进行了新的改进:next/future/image
。这些改进包括更少的客户端 JavaScript、更简单的扩展和样式图片方法、更好的可访问性以及原生浏览器延迟加载。
¥Next.js 12 introduced new improvements to the Image Component with a temporary import: next/future/image
. These improvements included less client-side JavaScript, easier ways to extend and style images, better accessibility, and native browser lazy loading.
在版本 13 中,此新行为现已成为 next/image
的默认行为。
¥In version 13, this new behavior is now the default for next/image
.
有两个代码模块可以帮助你迁移到新的图片组件:
¥There are two codemods to help you migrate to the new Image Component:
-
next-image-to-legacy-image
代码模式:安全自动地将next/image
导入重命名为next/legacy/image
。现有组件将保持相同的行为。¥
next-image-to-legacy-image
codemod: Safely and automatically renamesnext/image
imports tonext/legacy/image
. Existing components will maintain the same behavior. -
next-image-experimental
代码模式:危险地添加内联样式并删除未使用的属性。这将更改现有组件的行为以匹配新的默认值。要使用此 codemod,你需要先运行next-image-to-legacy-image
codemod。¥
next-image-experimental
codemod: Dangerously adds inline styles and removes unused props. This will change the behavior of existing components to match the new defaults. To use this codemod, you need to run thenext-image-to-legacy-image
codemod first.
<Link>
组件
¥<Link>
Component
<Link>
组件 不再需要手动添加 <a>
标签作为子标签。此行为是在 版本 12.2 中作为实验选项添加的,现在是默认行为。在 Next.js 13 中,<Link>
始终渲染 <a>
并允许你将 props 转发到底层标签。
¥The <Link>
Component no longer requires manually adding an <a>
tag as a child. This behavior was added as an experimental option in version 12.2 and is now the default. In Next.js 13, <Link>
always renders <a>
and allows you to forward props to the underlying tag.
例如:
¥For example:
import Link from 'next/link'
// Next.js 12: `<a>` has to be nested otherwise it's excluded
<Link href="/about">
<a>About</a>
</Link>
// Next.js 13: `<Link>` always renders `<a>` under the hood
<Link href="/about">
About
</Link>
要将链接升级到 Next.js 13,你可以使用 new-link
代码模式。
¥To upgrade your links to Next.js 13, you can use the new-link
codemod.
<Script>
组件
¥<Script>
Component
next/script
的行为已更新以支持 pages
和 app
,但需要进行一些更改以确保顺利迁移:
¥The behavior of next/script
has been updated to support both pages
and app
, but some changes need to be made to ensure a smooth migration:
-
将之前包含在
_document.js
中的所有beforeInteractive
脚本移至根布局文件 (app/layout.tsx
)。¥Move any
beforeInteractive
scripts you previously included in_document.js
to the root layout file (app/layout.tsx
). -
实验性的
worker
策略尚未在app
中运行,并且必须删除或修改用此策略表示的脚本以使用不同的策略(例如lazyOnload
)。¥The experimental
worker
strategy does not yet work inapp
and scripts denoted with this strategy will either have to be removed or modified to use a different strategy (e.g.lazyOnload
). -
onLoad
、onReady
和onError
处理程序在服务器组件中不起作用,因此请确保将它们移至 客户端组件 或完全删除它们。¥
onLoad
,onReady
, andonError
handlers will not work in Server Components so make sure to move them to a Client Component or remove them altogether.
字体优化
¥Font Optimization
此前,Next.js 已帮助你优化字体 内联字体 CSS。版本 13 引入了新的 next/font
模块,使你能够自定义字体加载体验,同时仍然确保出色的性能和隐私。pages
和 app
目录均支持 next/font
。
¥Previously, Next.js helped you optimize fonts by inlining font CSS. Version 13 introduces the new next/font
module which gives you the ability to customize your font loading experience while still ensuring great performance and privacy. next/font
is supported in both the pages
and app
directories.
虽然 内联 CSS 在 pages
中仍然有效,但在 app
中不起作用。你应该使用 next/font
来代替。
¥While inlining CSS still works in pages
, it does not work in app
. You should use next/font
instead.
请参阅 字体优化 页面以了解如何使用 next/font
。
¥See the Font Optimization page to learn how to use next/font
.
从 pages
迁移到 app
¥Migrating from pages
to app
🎥 监视:了解如何逐步采用 App Router → YouTube(16 分钟)。
¥🎥 Watch: Learn how to incrementally adopt the App Router → YouTube (16 minutes).
迁移到 App Router 可能是第一次使用 Next.js 构建于其之上的 React 功能,例如服务器组件、Suspense 等。当与新的 Next.js 功能(例如 特殊文件 和 layouts)相结合时,迁移意味着需要学习新的概念、思维模型和行为变化。
¥Moving to the App Router may be the first time using React features that Next.js builds on top of such as Server Components, Suspense, and more. When combined with new Next.js features such as special files and layouts, migration means new concepts, mental models, and behavioral changes to learn.
我们建议通过将迁移分解为更小的步骤来降低这些更新的综合复杂性。app
目录特意设计为与 pages
目录同时工作,以允许增量式逐页迁移。
¥We recommend reducing the combined complexity of these updates by breaking down your migration into smaller steps. The app
directory is intentionally designed to work simultaneously with the pages
directory to allow for incremental page-by-page migration.
-
app
目录支持嵌套路由和布局。了解更多。¥The
app
directory supports nested routes and layouts. Learn more. -
使用 定义路由 的嵌套文件夹和特殊的
page.js
文件使路由段可公开访问。了解更多。¥Use nested folders to define routes and a special
page.js
file to make a route segment publicly accessible. Learn more. -
特殊文件约定 用于为每个路由段创建 UI。最常见的特殊文件是
page.js
和layout.js
。¥Special file conventions are used to create UI for each route segment. The most common special files are
page.js
andlayout.js
.-
使用
page.js
定义路由特有的 UI。¥Use
page.js
to define UI unique to a route. -
使用
layout.js
定义在多个路由之间共享的 UI。¥Use
layout.js
to define UI that is shared across multiple routes. -
.js
、.jsx
或.tsx
文件扩展名可用于特殊文件。¥
.js
,.jsx
, or.tsx
file extensions can be used for special files.
-
-
你可以在
app
目录中并置其他文件,例如组件、样式、测试等。了解更多。¥You can colocate other files inside the
app
directory such as components, styles, tests, and more. Learn more. -
app
中的getServerSideProps
和getStaticProps
等数据获取函数已被 一个新的 API 替换。getStaticPaths
已替换为generateStaticParams
。¥Data fetching functions like
getServerSideProps
andgetStaticProps
have been replaced with a new API insideapp
.getStaticPaths
has been replaced withgenerateStaticParams
. -
pages/_app.js
和pages/_document.js
已替换为单个app/layout.js
根布局。了解更多。¥
pages/_app.js
andpages/_document.js
have been replaced with a singleapp/layout.js
root layout. Learn more. -
pages/_error.js
已替换为更细粒度的error.js
特殊文件。了解更多。¥
pages/_error.js
has been replaced with more granularerror.js
special files. Learn more. -
pages/404.js
已替换为not-found.js
文件。¥
pages/404.js
has been replaced with thenot-found.js
file. -
pages/api/*
API 路由已替换为route.js
(路由处理程序)特殊文件。¥
pages/api/*
API Routes have been replaced with theroute.js
(Route Handler) special file.
步骤 1:创建 app
目录
¥Step 1: Creating the app
directory
更新到最新的 Next.js 版本(需要 13.4 或更高版本):
¥Update to the latest Next.js version (requires 13.4 or greater):
npm install next@latest
然后,在项目的根目录(或 src/
目录)创建一个新的 app
目录。
¥Then, create a new app
directory at the root of your project (or src/
directory).
步骤 2:创建根布局
¥Step 2: Creating a Root Layout
在 app
目录中创建一个新的 app/layout.tsx
文件。这是一个 根布局,将应用于 app
内的所有路由。
¥Create a new app/layout.tsx
file inside the app
directory. This is a root layout that will apply to all routes inside app
.
export default function RootLayout({
// Layouts must accept a children prop.
// This will be populated with nested layouts or pages
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
export default function RootLayout({
// Layouts must accept a children prop.
// This will be populated with nested layouts or pages
children,
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
-
app
目录必须包含根布局。¥The
app
directory must include a root layout. -
根布局必须定义
<html>
和<body>
标签,因为 Next.js 不会自动创建它们¥The root layout must define
<html>
, and<body>
tags since Next.js does not automatically create them -
根布局替换
pages/_app.tsx
和pages/_document.tsx
文件。¥The root layout replaces the
pages/_app.tsx
andpages/_document.tsx
files. -
.js
、.jsx
或.tsx
扩展名可用于布局文件。¥
.js
,.jsx
, or.tsx
extensions can be used for layout files.
要管理 <head>
HTML 元素,你可以使用 内置 SEO 支持:
¥To manage <head>
HTML elements, you can use the built-in SEO support:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Home',
description: 'Welcome to Next.js',
}
export const metadata = {
title: 'Home',
description: 'Welcome to Next.js',
}
迁移 _document.js
和 _app.js
¥Migrating _document.js
and _app.js
如果你有现有的 _app
或 _document
文件,你可以将内容(例如全局样式)复制到根布局 (app/layout.tsx
)。app/layout.tsx
中的样式不适用于 pages/*
。迁移时应保留 _app
/_document
,以防止 pages/*
路由中断。完全迁移后,你就可以安全地删除它们。
¥If you have an existing _app
or _document
file, you can copy the contents (e.g. global styles) to the root layout (app/layout.tsx
). Styles in app/layout.tsx
will not apply to pages/*
. You should keep _app
/_document
while migrating to prevent your pages/*
routes from breaking. Once fully migrated, you can then safely delete them.
如果你正在使用任何 React Context 提供程序,则需要将它们移至 客户端组件。
¥If you are using any React Context providers, they will need to be moved to a Client Component.
将 getLayout()
模式迁移到布局(可选)
¥Migrating the getLayout()
pattern to Layouts (Optional)
Next.js 建议在 pages
目录中添加 页面组件的属性 以实现每页布局。此模式可以替换为 app
目录中对 嵌套布局 的原生支持。
¥Next.js recommended adding a property to Page components to achieve per-page layouts in the pages
directory. This pattern can be replaced with native support for nested layouts in the app
directory.
See before and after example
前
¥Before
export default function DashboardLayout({ children }) {
return (
<div>
<h2>My Dashboard</h2>
{children}
</div>
)
}
import DashboardLayout from '../components/DashboardLayout'
export default function Page() {
return <p>My Page</p>
}
Page.getLayout = function getLayout(page) {
return <DashboardLayout>{page}</DashboardLayout>
}
后
¥After
-
从
pages/dashboard/index.js
中删除Page.getLayout
属性,并沿着 迁移页面的步骤 到达app
目录。¥Remove the
Page.getLayout
property frompages/dashboard/index.js
and follow the steps for migrating pages to theapp
directory.export default function Page() {
return <p>My Page</p>
} -
将
DashboardLayout
的内容移动到新的 客户端组件 中以保留pages
目录行为。¥Move the contents of
DashboardLayout
into a new Client Component to retainpages
directory behavior.'use client' // this directive should be at top of the file, before any imports.
// This is a Client Component
export default function DashboardLayout({ children }) {
return (
<div>
<h2>My Dashboard</h2>
{children}
</div>
)
} -
将
DashboardLayout
导入到app
目录内的新layout.js
文件中。¥Import the
DashboardLayout
into a newlayout.js
file inside theapp
directory.import DashboardLayout from './DashboardLayout'
// This is a Server Component
export default function Layout({ children }) {
return <DashboardLayout>{children}</DashboardLayout>
} -
你可以逐步将
DashboardLayout.js
(客户端组件)的非交互部分移至layout.js
(服务器组件),以减少发送到客户端的组件 JavaScript 数量。¥You can incrementally move non-interactive parts of
DashboardLayout.js
(Client Component) intolayout.js
(Server Component) to reduce the amount of component JavaScript you send to the client.
步骤 3:迁移 next/head
¥Step 3: Migrating next/head
在 pages
目录中,next/head
React 组件用于管理 <head>
个 HTML 元素,例如 title
和 meta
。 在 app
目录中,next/head
被替换为新的 内置 SEO 支持。
¥In the pages
directory, the next/head
React component is used to manage <head>
HTML elements such as title
and meta
. In the app
directory, next/head
is replaced with the new built-in SEO support.
前:
¥Before:
import Head from 'next/head'
export default function Page() {
return (
<>
<Head>
<title>My page title</title>
</Head>
</>
)
}
import Head from 'next/head'
export default function Page() {
return (
<>
<Head>
<title>My page title</title>
</Head>
</>
)
}
后:
¥After:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'My Page Title',
}
export default function Page() {
return '...'
}
export const metadata = {
title: 'My Page Title',
}
export default function Page() {
return '...'
}
步骤 4:迁移页面
¥Step 4: Migrating Pages
-
app
目录 中的页面默认为 服务器组件。这与pages
目录不同,其中页面为 客户端组件。¥Pages in the
app
directory are Server Components by default. This is different from thepages
directory where pages are Client Components. -
数据获取 在
app
中发生了变化。getServerSideProps
、getStaticProps
和getInitialProps
已替换为更简单的 API。¥Data fetching has changed in
app
.getServerSideProps
,getStaticProps
andgetInitialProps
have been replaced with a simpler API. -
app
目录使用 定义路由 的嵌套文件夹和特殊的page.js
文件来使路由段可公开访问。¥The
app
directory uses nested folders to define routes and a specialpage.js
file to make a route segment publicly accessible. -
pages
目录app
目录路由 index.js
page.js
/
about.js
about/page.js
/about
blog/[slug].js
blog/[slug]/page.js
/blog/post-1
我们建议将页面的迁移分为两个主要步骤:
¥We recommend breaking down the migration of a page into two main steps:
-
步骤 1:将默认导出的页面组件移动到新的客户端组件中。
¥Step 1: Move the default exported Page Component into a new Client Component.
-
步骤 2:将新的客户端组件导入到
app
目录内的新page.js
文件中。¥Step 2: Import the new Client Component into a new
page.js
file inside theapp
directory.
很高兴知道:这是最简单的迁移路径,因为它具有与
pages
目录最相似的行为。¥Good to know: This is the easiest migration path because it has the most comparable behavior to the
pages
directory.
步骤 1:创建一个新的客户端组件
¥Step 1: Create a new Client Component
-
在导出客户端组件的
app
目录(即app/home-page.tsx
或类似目录)内创建一个新的单独文件。要定义客户端组件,请将'use client'
指令添加到文件顶部(在任何导入之前)。¥Create a new separate file inside the
app
directory (i.e.app/home-page.tsx
or similar) that exports a Client Component. To define Client Components, add the'use client'
directive to the top of the file (before any imports).-
与页面路由类似,有一个 优化步骤 在初始页面加载时将客户端组件预渲染为静态 HTML。
¥Similar to the Pages Router, there is an optimization step to prerender Client Components to static HTML on the initial page load.
-
-
将默认导出的页面组件从
pages/index.js
移动到app/home-page.tsx
。¥Move the default exported page component from
pages/index.js
toapp/home-page.tsx
.
'use client'
// This is a Client Component (same as components in the `pages` directory)
// It receives data as props, has access to state and effects, and is
// prerendered on the server during the initial page load.
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
'use client'
// This is a Client Component. It receives data as props and
// has access to state and effects just like Page components
// in the `pages` directory.
export default function HomePage({ recentPosts }) {
return (
<div>
{recentPosts.map((post) => (
<div key={post.id}>{post.title}</div>
))}
</div>
)
}
步骤 2:创建一个新页面
¥Step 2: Create a new page
-
在
app
目录中创建一个新的app/page.tsx
文件。默认情况下这是一个服务器组件。¥Create a new
app/page.tsx
file inside theapp
directory. This is a Server Component by default. -
将
home-page.tsx
客户端组件导入页面。¥Import the
home-page.tsx
Client Component into the page. -
如果你在
pages/index.js
中获取数据,请使用新的 数据获取 API 将数据获取逻辑直接移至服务器组件中。有关详细信息,请参阅 数据获取升级指南。¥If you were fetching data in
pages/index.js
, move the data fetching logic directly into the Server Component using the new data fetching APIs. See the data fetching upgrade guide for more details.// Import your Client Component
import HomePage from './home-page'
async function getPosts() {
const res = await fetch('https://...')
const posts = await res.json()
return posts
}
export default async function Page() {
// Fetch data directly in a Server Component
const recentPosts = await getPosts()
// Forward fetched data to your Client Component
return <HomePage recentPosts={recentPosts} />
}// Import your Client Component
import HomePage from './home-page'
async function getPosts() {
const res = await fetch('https://...')
const posts = await res.json()
return posts
}
export default async function Page() {
// Fetch data directly in a Server Component
const recentPosts = await getPosts()
// Forward fetched data to your Client Component
return <HomePage recentPosts={recentPosts} />
} -
如果你之前的页面使用了
useRouter
,你将需要更新到新的路由钩子。了解更多。¥If your previous page used
useRouter
, you'll need to update to the new routing hooks. Learn more. -
启动你的开发服务器并访问
http://localhost:3000
。你应该看到现有的索引路由,现在通过应用目录提供服务。¥Start your development server and visit
http://localhost:3000
. You should see your existing index route, now served through the app directory.
步骤 5:迁移路由钩子
¥Step 5: Migrating Routing Hooks
添加了新路由以支持 app
目录中的新行为。
¥A new router has been added to support the new behavior in the app
directory.
在 app
中,你应该使用从 next/navigation
导入的三个新钩子:useRouter()
、usePathname()
和 useSearchParams()
。
¥In app
, you should use the three new hooks imported from next/navigation
: useRouter()
, usePathname()
, and useSearchParams()
.
-
新的
useRouter
钩子是从next/navigation
导入的,与pages
中从next/router
导入的useRouter
钩子具有不同的行为。¥The new
useRouter
hook is imported fromnext/navigation
and has different behavior to theuseRouter
hook inpages
which is imported fromnext/router
.-
useRouter
钩从next/router
导入 在app
目录中不受支持,但可以在pages
目录中继续使用。¥The
useRouter
hook imported fromnext/router
is not supported in theapp
directory but can continue to be used in thepages
directory.
-
-
新的
useRouter
不返回pathname
字符串。请改用单独的usePathname
钩子。¥The new
useRouter
does not return thepathname
string. Use the separateusePathname
hook instead. -
新的
useRouter
不返回query
对象。搜索参数和动态路由参数现在是分开的。改用useSearchParams
和useParams
钩子。¥The new
useRouter
does not return thequery
object. Search parameters and dynamic route parameters are now separate. Use theuseSearchParams
anduseParams
hooks instead. -
你可以使用
useSearchParams
和usePathname
一起监听页面变化。有关详细信息,请参阅 路由事件 部分。¥You can use
useSearchParams
andusePathname
together to listen to page changes. See the Router Events section for more details. -
这些新钩子仅在客户端组件中受支持。它们不能在服务器组件中使用。
¥These new hooks are only supported in Client Components. They cannot be used in Server Components.
'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
export default function ExampleClientComponent() {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
// ...
}
'use client'
import { useRouter, usePathname, useSearchParams } from 'next/navigation'
export default function ExampleClientComponent() {
const router = useRouter()
const pathname = usePathname()
const searchParams = useSearchParams()
// ...
}
此外,新的 useRouter
钩子还有以下变化:
¥In addition, the new useRouter
hook has the following changes:
-
isFallback
已被删除,因为fallback
具有 被替换。¥
isFallback
has been removed becausefallback
has been replaced. -
locale
、locales
、defaultLocales
、domainLocales
值已被删除,因为app
目录中不再需要内置 i18n Next.js 功能。了解有关 i18n 的更多信息。¥The
locale
,locales
,defaultLocales
,domainLocales
values have been removed because built-in i18n Next.js features are no longer necessary in theapp
directory. Learn more about i18n. -
basePath
已被删除。替代方案不会成为useRouter
的一部分。目前尚未实现。¥
basePath
has been removed. The alternative will not be part ofuseRouter
. It has not yet been implemented. -
asPath
已被删除,因为as
的概念已从新路由中删除。¥
asPath
has been removed because the concept ofas
has been removed from the new router. -
isReady
已被删除,因为不再需要它。在 静态渲染 期间,任何使用useSearchParams()
钩子的组件都将跳过预渲染步骤,而是在运行时在客户端上渲染。¥
isReady
has been removed because it is no longer necessary. During static rendering, any component that uses theuseSearchParams()
hook will skip the prerendering step and instead be rendered on the client at runtime. -
route
已被删除。usePathname
或useSelectedLayoutSegments()
提供替代方案。¥
route
has been removed.usePathname
oruseSelectedLayoutSegments()
provide an alternative.
¥View the useRouter()
API reference.
在 pages
和 app
之间共享组件
¥Sharing components between pages
and app
为了保持 pages
和 app
路由之间的组件兼容,请参阅 来自 next/compat/router
的 useRouter
钩子。这是来自 pages
目录的 useRouter
钩子,但旨在用于在路由之间共享组件时。一旦你准备好仅在 app
路由上使用它,请更新到新的 useRouter
从 next/navigation
。
¥To keep components compatible between the pages
and app
routers, refer to the useRouter
hook from next/compat/router
.
This is the useRouter
hook from the pages
directory, but intended to be used while sharing components between routers. Once you are ready to use it only on the app
router, update to the new useRouter
from next/navigation
.
步骤 6:迁移数据获取方法
¥Step 6: Migrating Data Fetching Methods
pages
目录使用 getServerSideProps
和 getStaticProps
来获取页面数据。在 app
目录中,这些以前的数据获取函数被基于 fetch()
和 async
React Server 组件构建的 更简单的 API 所取代。
¥The pages
directory uses getServerSideProps
and getStaticProps
to fetch data for pages. Inside the app
directory, these previous data fetching functions are replaced with a simpler API built on top of fetch()
and async
React Server Components.
export default async function Page() {
// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
const staticData = await fetch(`https://...`, { cache: 'force-cache' })
// This request should be refetched on every request.
// Similar to `getServerSideProps`.
const dynamicData = await fetch(`https://...`, { cache: 'no-store' })
// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
})
return <div>...</div>
}
export default async function Page() {
// This request should be cached until manually invalidated.
// Similar to `getStaticProps`.
// `force-cache` is the default and can be omitted.
const staticData = await fetch(`https://...`, { cache: 'force-cache' })
// This request should be refetched on every request.
// Similar to `getServerSideProps`.
const dynamicData = await fetch(`https://...`, { cache: 'no-store' })
// This request should be cached with a lifetime of 10 seconds.
// Similar to `getStaticProps` with the `revalidate` option.
const revalidatedData = await fetch(`https://...`, {
next: { revalidate: 10 },
})
return <div>...</div>
}
服务器端渲染(getServerSideProps
)
¥Server-side Rendering (getServerSideProps
)
在 pages
目录中,getServerSideProps
用于在服务器上获取数据并将 props 转发到文件中默认导出的 React 组件。页面的初始 HTML 是从服务器预渲染的,然后是浏览器中的 "hydrating" 页面(使其具有交互性)。
¥In the pages
directory, getServerSideProps
is used to fetch data on the server and forward props to the default exported React component in the file. The initial HTML for the page is prerendered from the server, followed by "hydrating" the page in the browser (making it interactive).
// `pages` directory
export async function getServerSideProps() {
const res = await fetch(`https://...`)
const projects = await res.json()
return { props: { projects } }
}
export default function Dashboard({ projects }) {
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}
在 App Router 中,我们可以使用 服务器组件 将数据获取放在 React 组件内。这使我们能够向客户端发送更少的 JavaScript,同时保留服务器渲染的 HTML。
¥In the App Router, we can colocate our data fetching inside our React components using Server Components. This allows us to send less JavaScript to the client, while maintaining the rendered HTML from the server.
通过将 cache
选项设置为 no-store
,我们可以指示获取的数据应该是 永远不会被缓存。这与 pages
目录中的 getServerSideProps
类似。
¥By setting the cache
option to no-store
, we can indicate that the fetched data should never be cached. This is similar to getServerSideProps
in the pages
directory.
// `app` directory
// This function can be named anything
async function getProjects() {
const res = await fetch(`https://...`, { cache: 'no-store' })
const projects = await res.json()
return projects
}
export default async function Dashboard() {
const projects = await getProjects()
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}
// `app` directory
// This function can be named anything
async function getProjects() {
const res = await fetch(`https://...`, { cache: 'no-store' })
const projects = await res.json()
return projects
}
export default async function Dashboard() {
const projects = await getProjects()
return (
<ul>
{projects.map((project) => (
<li key={project.id}>{project.name}</li>
))}
</ul>
)
}
访问请求对象
¥Accessing Request Object
在 pages
目录中,你可以基于 Node.js HTTP API 检索基于请求的数据。
¥In the pages
directory, you can retrieve request-based data based on the Node.js HTTP API.
例如,你可以从 getServerSideProps
检索 req
对象,并使用它来检索请求的 cookie 和标头。
¥For example, you can retrieve the req
object from getServerSideProps
and use it to retrieve the request's cookies and headers.
// `pages` directory
export async function getServerSideProps({ req, query }) {
const authHeader = req.getHeaders()['authorization'];
const theme = req.cookies['theme'];
return { props: { ... }}
}
export default function Page(props) {
return ...
}
app
目录公开了新的只读函数来检索请求数据:
¥The app
directory exposes new read-only functions to retrieve request data:
-
headers
:基于 Web Headers API,可以在 服务器组件 内部使用来检索请求标头。¥
headers
: Based on the Web Headers API, and can be used inside Server Components to retrieve request headers. -
cookies
:基于 Web Cookies API,可以在 服务器组件 内部使用来检索 cookie。¥
cookies
: Based on the Web Cookies API, and can be used inside Server Components to retrieve cookies.
// `app` directory
import { cookies, headers } from 'next/headers'
async function getData() {
const authHeader = (await headers()).get('authorization')
return '...'
}
export default async function Page() {
// You can use `cookies` or `headers` inside Server Components
// directly or in your data fetching function
const theme = (await cookies()).get('theme')
const data = await getData()
return '...'
}
// `app` directory
import { cookies, headers } from 'next/headers'
async function getData() {
const authHeader = (await headers()).get('authorization')
return '...'
}
export default async function Page() {
// You can use `cookies` or `headers` inside Server Components
// directly or in your data fetching function
const theme = (await cookies()).get('theme')
const data = await getData()
return '...'
}
静态站点生成 (getStaticProps
)
¥Static Site Generation (getStaticProps
)
在 pages
目录中,getStaticProps
函数用于在构建时预渲染页面。此函数可用于从外部 API 或直接从数据库获取数据,并将这些数据在构建期间生成时传递到整个页面。
¥In the pages
directory, the getStaticProps
function is used to pre-render a page at build time. This function can be used to fetch data from an external API or directly from a database, and pass this data down to the entire page as it's being generated during the build.
// `pages` directory
export async function getStaticProps() {
const res = await fetch(`https://...`)
const projects = await res.json()
return { props: { projects } }
}
export default function Index({ projects }) {
return projects.map((project) => <div>{project.name}</div>)
}
在 app
目录下,用 fetch()
取数据会默认到 cache: 'force-cache'
,cache: 'force-cache'
会缓存请求数据,直到手动失效。这与 pages
目录中的 getStaticProps
类似。
¥In the app
directory, data fetching with fetch()
will default to cache: 'force-cache'
, which will cache the request data until manually invalidated. This is similar to getStaticProps
in the pages
directory.
// `app` directory
// This function can be named anything
async function getProjects() {
const res = await fetch(`https://...`)
const projects = await res.json()
return projects
}
export default async function Index() {
const projects = await getProjects()
return projects.map((project) => <div>{project.name}</div>)
}
动态路径 (getStaticPaths
)
¥Dynamic paths (getStaticPaths
)
在 pages
目录中,getStaticPaths
函数用于定义应在构建时预渲染的动态路径。
¥In the pages
directory, the getStaticPaths
function is used to define the dynamic paths that should be pre-rendered at build time.
// `pages` directory
import PostLayout from '@/components/post-layout'
export async function getStaticPaths() {
return {
paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
}
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
return { props: { post } }
}
export default function Post({ post }) {
return <PostLayout post={post} />
}
在 app
目录中,getStaticPaths
被替换为 generateStaticParams
。
¥In the app
directory, getStaticPaths
is replaced with generateStaticParams
.
generateStaticParams
的行为与 getStaticPaths
类似,但具有用于返回路由参数的简化 API,并且可以在 layouts 内部使用。generateStaticParams
的返回形状是一个段数组,而不是嵌套的 param
对象数组或解析路径字符串。
¥generateStaticParams
behaves similarly to getStaticPaths
, but has a simplified API for returning route parameters and can be used inside layouts. The return shape of generateStaticParams
is an array of segments instead of an array of nested param
objects or a string of resolved paths.
// `app` directory
import PostLayout from '@/components/post-layout'
export async function generateStaticParams() {
return [{ id: '1' }, { id: '2' }]
}
async function getPost(params) {
const res = await fetch(`https://.../posts/${params.id}`)
const post = await res.json()
return post
}
export default async function Post({ params }) {
const post = await getPost(params)
return <PostLayout post={post} />
}
对于 app
目录中的新模型,使用名称 generateStaticParams
比 getStaticPaths
更合适。get
前缀被替换为更具描述性的 generate
,现在不再需要 getStaticProps
和 getServerSideProps
,单独使用它会更好。Paths
后缀被替换为 Params
,这更适合具有多个动态段的嵌套路由。
¥Using the name generateStaticParams
is more appropriate than getStaticPaths
for the new model in the app
directory. The get
prefix is replaced with a more descriptive generate
, which sits better alone now that getStaticProps
and getServerSideProps
are no longer necessary. The Paths
suffix is replaced by Params
, which is more appropriate for nested routing with multiple dynamic segments.
更换 fallback
¥Replacing fallback
在 pages
目录中,从 getStaticPaths
返回的 fallback
属性用于定义在构建时未预渲染的页面的行为。此属性可以设置为 true
以在生成页面时显示后备页面,设置为 false
以显示 404 页面,或设置为 blocking
以在请求时生成页面。
¥In the pages
directory, the fallback
property returned from getStaticPaths
is used to define the behavior of a page that isn't pre-rendered at build time. This property can be set to true
to show a fallback page while the page is being generated, false
to show a 404 page, or blocking
to generate the page at request time.
// `pages` directory
export async function getStaticPaths() {
return {
paths: [],
fallback: 'blocking'
};
}
export async function getStaticProps({ params }) {
...
}
export default function Post({ post }) {
return ...
}
在 app
目录中,config.dynamicParams
property 控制 generateStaticParams
之外的参数的处理方式:
¥In the app
directory the config.dynamicParams
property controls how params outside of generateStaticParams
are handled:
-
true
:(默认)generateStaticParams
中未包含的动态段是按需生成的。¥**
true
**: (default) Dynamic segments not included ingenerateStaticParams
are generated on demand. -
false
:未包含在generateStaticParams
中的动态段将返回 404。¥**
false
**: Dynamic segments not included ingenerateStaticParams
will return a 404.
这替换了 pages
目录中 getStaticPaths
的 fallback: true | false | 'blocking'
选项。fallback: 'blocking'
选项不包含在 dynamicParams
中,因为 'blocking'
和 true
之间的差异对于流式传输来说可以忽略不计。
¥This replaces the fallback: true | false | 'blocking'
option of getStaticPaths
in the pages
directory. The fallback: 'blocking'
option is not included in dynamicParams
because the difference between 'blocking'
and true
is negligible with streaming.
// `app` directory
export const dynamicParams = true;
export async function generateStaticParams() {
return [...]
}
async function getPost(params) {
...
}
export default async function Post({ params }) {
const post = await getPost(params);
return ...
}
当 dynamicParams
设置为 true
(默认值)时,当请求尚未生成的路由段时,它将被服务器渲染并缓存。
¥With dynamicParams
set to true
(the default), when a route segment is requested that hasn't been generated, it will be server-rendered and cached.
增量静态再生(getStaticProps
和 revalidate
)
¥Incremental Static Regeneration (getStaticProps
with revalidate
)
在 pages
目录中,getStaticProps
功能允许你添加 revalidate
字段,以在一定时间后自动重新生成页面。
¥In the pages
directory, the getStaticProps
function allows you to add a revalidate
field to automatically regenerate a page after a certain amount of time.
// `pages` directory
export async function getStaticProps() {
const res = await fetch(`https://.../posts`)
const posts = await res.json()
return {
props: { posts },
revalidate: 60,
}
}
export default function Index({ posts }) {
return (
<Layout>
<PostList posts={posts} />
</Layout>
)
}
在 app
目录中,使用 fetch()
获取数据可以使用 revalidate
,revalidate
会将请求缓存指定的秒数。
¥In the app
directory, data fetching with fetch()
can use revalidate
, which will cache the request for the specified amount of seconds.
// `app` directory
async function getPosts() {
const res = await fetch(`https://.../posts`, { next: { revalidate: 60 } })
const data = await res.json()
return data.posts
}
export default async function PostList() {
const posts = await getPosts()
return posts.map((post) => <div>{post.name}</div>)
}
API 路由
¥API Routes
API 路由继续在 pages/api
目录中工作,没有任何更改。但是,它们已被 app
目录中的 路由处理程序 替换。
¥API Routes continue to work in the pages/api
directory without any changes. However, they have been replaced by Route Handlers in the app
directory.
路由处理程序允许你使用 Web 请求 和 响应 API 为给定路由创建自定义请求处理程序。
¥Route Handlers allow you to create custom request handlers for a given route using the Web Request and Response APIs.
export async function GET(request: Request) {}
export async function GET(request) {}
很高兴知道:如果你之前使用 API 路由从客户端调用外部 API,现在可以使用 服务器组件 来安全地获取数据。了解有关 数据获取 的更多信息。
¥Good to know: If you previously used API routes to call an external API from the client, you can now use Server Components instead to securely fetch data. Learn more about data fetching.
步骤 7:样式
¥Step 7: Styling
在 pages
目录中,全局样式表仅限于 pages/_app.js
。有了 app
目录,这个限制就被解除了。全局样式可以添加到任何布局、页面或组件中。
¥In the pages
directory, global stylesheets are restricted to only pages/_app.js
. With the app
directory, this restriction has been lifted. Global styles can be added to any layout, page, or component.
Tailwind CSS
如果你使用的是 Tailwind CSS,则需要将 app
目录添加到 tailwind.config.js
文件中:
¥If you're using Tailwind CSS, you'll need to add the app
directory to your tailwind.config.js
file:
module.exports = {
content: [
'./app/**/*.{js,ts,jsx,tsx,mdx}', // <-- Add this line
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
],
}
你还需要在 app/layout.js
文件中导入全局样式:
¥You'll also need to import your global styles in your app/layout.js
file:
import '../styles/globals.css'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
了解有关 使用 Tailwind CSS 进行样式设置 的更多信息
¥Learn more about styling with Tailwind CSS
代码模组
¥Codemods
Next.js 提供 Codemod 转换,以帮助在功能被弃用时升级代码库。请参阅 代码模组 了解更多信息。
¥Next.js provides Codemod transformations to help upgrade your codebase when a feature is deprecated. See Codemods for more information.