App Router 增量采用指南
本指南将帮助你:
升级
Node.js 版本
现在最低 Node.js 版本要求为 v18.17。更多信息请参阅 Node.js 文档。
Next.js 版本
要更新到 Next.js 13 版本,请使用你喜欢的包管理器运行以下命令:
ESLint 版本
如果你使用 ESLint,需要升级 ESLint 版本:
值得注意的是:你可能需要在 VS Code 中重启 ESLint 服务器才能使 ESLint 更改生效。打开命令面板 (Mac 上是
cmd+shift+p
;Windows 上是ctrl+shift+p
) 并搜索ESLint: Restart ESLint Server
。
后续步骤
更新后,请查看以下部分了解后续步骤:
- 升级新功能:帮助你升级到新功能 (如改进的 Image 和 Link 组件) 的指南。
- 从
pages
迁移到app
目录:帮助你逐步从pages
迁移到app
目录的分步指南。
升级新功能
Next.js 13 引入了新的 App Router,带来了新的功能和约定。新的 Router 在 app
目录中可用,并与 pages
目录共存。
升级到 Next.js 13 不要求使用新的 App Router。你可以继续使用 pages
,同时使用在两个目录中都可用的新功能,例如更新后的 Image 组件、Link 组件、Script 组件 和 字体优化。
<Image/>
组件
Next.js 12 为 Image 组件引入了新的改进,使用临时导入:next/future/image
。这些改进包括更少的客户端 JavaScript、更易扩展和样式化图片的方式、更好的可访问性以及原生浏览器懒加载。
在版本 13 中,这种新行为现在是 next/image
的默认行为。
有两个 codemod 可以帮助你迁移到新的 Image 组件:
next-image-to-legacy-image
codemod:安全且自动地将next/image
导入重命名为next/legacy/image
。现有组件将保持相同的行为。next-image-experimental
codemod:危险地添加内联样式并删除未使用的属性。这将改变现有组件的行为以匹配新的默认设置。要使用这个 codemod,你需要先运行next-image-to-legacy-image
codemod。
<Link>
组件
<Link>
组件 不再需要手动添加 <a>
标签作为子元素。这个行为在 12.2 版本 中作为实验性选项添加,现在成为默认行为。在 Next.js 13 中,<Link>
始终渲染 <a>
,并允许你将属性转发到底层标签。
例如:
要将链接升级到 Next.js 13,可以使用 new-link
codemod。
<Script>
组件
next/script
的行为已更新以支持 pages
和 app
,但需要进行一些更改以确保平稳迁移:
- 将你之前在
_document.js
中包含的任何beforeInteractive
脚本移到根布局文件 (app/layout.tsx
)。 - 实验性的
worker
策略在app
中尚不可用,使用此策略的脚本需要移除或修改为使用不同的策略 (例如lazyOnload
)。 onLoad
、onReady
和onError
处理程序在服务器组件中不起作用,请确保将它们移到 客户端组件 中或完全移除。
字体优化
以前,Next.js 通过 内联字体 CSS 来帮助你优化字体。版本 13 引入了新的 next/font
模块,让你能够自定义字体加载体验,同时仍确保出色的性能和隐私。next/font
在 pages
和 app
目录中都受支持。
虽然 内联 CSS 在 pages
中仍然有效,但在 app
中不起作用。你应该改用 next/font
。
请查看 字体优化 页面了解如何使用 next/font
。
从 pages
迁移到 app
🎥 观看: 了解如何逐步采用 App Router → YouTube (16 分钟)。
迁移到 App Router 可能是你首次使用 Next.js 在其基础上构建的 React 功能,如服务器组件、Suspense 等。当与 Next.js 的新功能 (如 特殊文件 和 布局) 结合时,迁移意味着需要学习新概念、心智模型和行为变化。
我们建议通过将迁移分解为更小的步骤来降低这些更新的复杂性。app
目录的设计初衷是与 pages
目录同时工作,以允许逐页增量迁移。
app
目录支持嵌套路由 和 布局。了解更多。- 使用嵌套文件夹来 定义路由,并使用特殊的
page.js
文件使路由段可公开访问。了解更多。 - 特殊文件约定 用于为每个路由段创建 UI。最常见的特殊文件是
page.js
和layout.js
。- 使用
page.js
定义路由特有的 UI。 - 使用
layout.js
定义多个路由共享的 UI。 - 特殊文件可以使用
.js
、.jsx
或.tsx
文件扩展名。
- 使用
- 你可以在
app
目录中放置其他文件,如组件、样式、测试等。了解更多。 - 数据获取函数如
getServerSideProps
和getStaticProps
已被app
中的 新 API 替代。getStaticPaths
已被generateStaticParams
替代。 pages/_app.js
和pages/_document.js
已被单个app/layout.js
根布局替代。了解更多。pages/_error.js
已被更细粒度的error.js
特殊文件替代。了解更多。pages/404.js
已被not-found.js
文件替代。pages/api/*
API 路由已被route.js
(路由处理程序) 特殊文件替代。
步骤 1:创建 app
目录
更新到最新的 Next.js 版本 (需要 13.4 或更高版本):
然后,在项目根目录 (或 src/
目录) 创建一个新的 app
目录。
步骤 2:创建根布局
在 app
目录中创建一个新的 app/layout.tsx
文件。这是一个 根布局,它将应用于 app
内的所有路由。
app
目录 必须 包含一个根布局。- 根布局必须定义
<html>
和<body>
标签,因为 Next.js 不会自动创建它们 - 根布局替代了
pages/_app.tsx
和pages/_document.tsx
文件。 - 布局文件可以使用
.js
、.jsx
或.tsx
扩展名。
要管理 <head>
HTML 元素,你可以使用 内置的 SEO 支持:
迁移 _document.js
和 _app.js
如果你有现有的 _app
或 _document
文件,可以将其内容 (例如全局样式) 复制到根布局 (app/layout.tsx
) 中。app/layout.tsx
中的样式将 不会 应用于 pages/*
。在迁移过程中,你应该保留 _app
/_document
以防止 pages/*
路由中断。一旦完全迁移,你就可以安全地删除它们。
如果你使用任何 React Context 提供者,它们需要移动到一个 客户端组件 中。
将 getLayout()
模式迁移到布局 (可选)
Next.js 曾建议在 pages
目录中为页面组件添加属性来实现每个页面的布局。现在可以用 app
目录中原生支持的嵌套布局来替代这种模式。
查看迁移前后的示例
之前
之后
步骤 3:迁移 next/head
在 pages
目录中,next/head
React 组件用于管理 <head>
HTML 元素,如 title
和 meta
。在 app
目录中,next/head
被新的内置 SEO 支持所取代。
之前:
之后:
步骤 4:迁移页面
app
目录中的页面默认是服务器组件。这与pages
目录不同,pages
目录中的页面是客户端组件。app
中的数据获取发生了变化。getServerSideProps
、getStaticProps
和getInitialProps
被更简单的 API 所取代。app
目录使用嵌套文件夹来定义路由,并使用特殊的page.js
文件来使路由段公开可访问。-
pages
目录app
目录路由 index.js
page.js
/
about.js
about/page.js
/about
blog/[slug].js
blog/[slug]/page.js
/blog/post-1
我们建议将页面迁移分为两个主要步骤:
- 步骤 1:将默认导出的页面组件移到一个新的客户端组件中。
- 步骤 2:将新的客户端组件导入到
app
目录中的新page.js
文件中。
值得注意的是:这是最简单的迁移路径,因为它的行为与
pages
目录最为相似。
步骤 1:创建一个新的客户端组件
- 在
app
目录中创建一个新的单独文件 (例如app/home-page.tsx
或类似名称),导出一个客户端组件。要定义客户端组件,在文件顶部 (在任何导入之前) 添加'use client'
指令。- 与 Pages Router 类似,有一个优化步骤,在初始页面加载时将客户端组件预渲染为静态 HTML。
- 将
pages/index.js
中默认导出的页面组件移到app/home-page.tsx
。
步骤 2:创建一个新页面
-
在
app
目录中创建一个新的app/page.tsx
文件。这默认是一个服务器组件。 -
将
home-page.tsx
客户端组件导入到页面中。 -
如果你之前在
pages/index.js
中获取数据,请使用新的数据获取 API 直接在服务器组件中移动数据获取逻辑。有关更多详细信息,请参阅数据获取升级指南。 -
如果你之前的页面使用了
useRouter
,你需要更新为新的路由 hook。了解更多。 -
启动你的开发服务器并访问
http://localhost:3000
。你应该能看到你现有的索引路由,现在是通过 app 目录提供服务的。
步骤 5:迁移路由 hook
为了支持 app
目录中的新行为,添加了一个新的路由器。
在 app
中,你应该使用从 next/navigation
导入的三个新 hook:useRouter()
、usePathname()
和 useSearchParams()
。
- 新的
useRouter
hook 从next/navigation
导入,其行为与pages
中从next/router
导入的useRouter
hook 不同。- 从
next/router
导入的useRouter
hook 在app
目录中不受支持,但可以继续在pages
目录中使用。
- 从
- 新的
useRouter
不返回pathname
字符串。请改用单独的usePathname
hook。 - 新的
useRouter
不再返回query
对象。搜索参数和动态路由参数现在是分开的。请改用useSearchParams
和useParams
hooks。 - 你可以同时使用
useSearchParams
和usePathname
来监听页面变化。有关更多详细信息,请参阅路由器事件部分。 - 这些新 hook 仅在客户端组件中受支持。它们不能在服务器组件中使用。
此外,新的 useRouter
hook 有以下变化:
- 移除了
isFallback
,因为fallback
已经被替换。 - 移除了
locale
、locales
、defaultLocales
、domainLocales
值,因为在app
目录中不再需要内置的 Next.js 国际化功能。了解更多关于国际化的信息。 - 移除了
basePath
。替代方案将不会是useRouter
的一部分。它尚未实现。 - 移除了
asPath
,因为新路由器中已经移除了as
的概念。 - 移除了
isReady
,因为它不再需要。在静态渲染期间,任何使用useSearchParams()
hook 的组件都将跳过预渲染步骤,而是在运行时在客户端渲染。 - 移除了
route
,可以使用usePathname
或useSelectedLayoutSegments()
作为替代方案。
在 pages
和 app
之间共享组件
要保持组件在 pages
和 app
路由器之间的兼容性,请参考 next/compat/router
中的 useRouter
hook。
这是来自 pages
目录的 useRouter
hook,但旨在用于在路由器之间共享组件。一旦你准备好只在 app
路由器中使用它,就更新到新的 next/navigation
中的 useRouter
。
步骤 6:迁移数据获取方法
pages
目录使用 getServerSideProps
和 getStaticProps
来为页面获取数据。在 app
目录中,这些先前的数据获取函数被替换为基于 fetch()
和异步 React 服务器组件的更简单的 API。
服务器端渲染 (getServerSideProps
)
在 pages
目录中,getServerSideProps
用于在服务器上获取数据并将 props 转发给文件中默认导出的 React 组件。页面的初始 HTML 从服务器预渲染,然后在浏览器中进行"水合" (使其具有交互性)。
在 App Router 中,我们可以使用 Server Components 将数据获取代码直接放在 React 组件中。这样可以减少发送到客户端的 JavaScript 代码量,同时保留服务器端渲染的 HTML。
通过将 cache
选项设置为 no-store
,我们可以指示获取的数据永远不要缓存。这类似于 pages
目录中的 getServerSideProps
。
访问请求对象
在 pages
目录中,你可以基于 Node.js HTTP API 检索请求相关的数据。
例如,你可以从 getServerSideProps
中检索 req
对象,并使用它来检索请求的 cookies 和 headers。
app
目录公开了新的只读函数来检索请求数据:
headers
:基于 Web Headers API,可以在服务器组件中使用来获取请求 headers。cookies
:基于 Web Cookies API,可以在服务器组件中使用来获取 cookies。
静态生成 (getStaticProps
)
在 pages
目录中,getStaticProps
函数用于在构建时预渲染页面。这个函数可以用来从外部 API 或直接从数据库获取数据,并在页面生成过程中将这些数据传递给整个页面。
在 app
目录中,使用 fetch()
进行数据获取时,会默认设置 cache: 'force-cache'
,这将缓存请求数据直到手动失效。这与 pages
目录中的 getStaticProps
类似。
动态路径 (getStaticPaths
)
在 pages
目录中,getStaticPaths
函数用于定义在构建时应该预渲染的动态路径。
在 app
目录中,getStaticPaths
被 generateStaticParams
替代。
generateStaticParams
的行为与 getStaticPaths
类似,但它简化了返回路由参数的 API,并且可以在 layouts 中使用。generateStaticParams
的返回形式是一个段数组,而不是嵌套的 param
对象数组或解析后的路径字符串。
在 app
目录的新模型中,使用 generateStaticParams
这个名称比 getStaticPaths
更合适。get
前缀被更具描述性的 generate
替代,这在 getStaticProps
和 getServerSideProps
不再必要的情况下更为恰当。Paths
后缀被 Params
替代,这对于具有多个动态段的嵌套路由更为合适。
替换 fallback
在 pages
目录中,从 getStaticPaths
返回的 fallback
属性用于定义构建时未预渲染的页面的行为。这个属性可以设置为 true
以在页面生成时显示一个回退页面,设置为 false
以显示 404 页面,或设置为 blocking
以在请求时生成页面。
在 app
目录中,config.dynamicParams
属性 控制了如何处理 generateStaticParams
之外的参数:
true
:(默认) 不包含在generateStaticParams
中的动态段将按需生成。false
:不包含在generateStaticParams
中的动态段将返回 404。
这取代了 pages
目录中 getStaticPaths
的 fallback: true | false | 'blocking'
选项。dynamicParams
中不包含 fallback: 'blocking'
选项,因为在流式传输的情况下,'blocking' 和 true
之间的区别可以忽略不计。
当 dynamicParams
设置为 true
(默认值) 时,如果请求了一个尚未生成的路由段,它将被服务器渲染并缓存。
增量静态再生成 (getStaticProps
与 revalidate
)
在 pages
目录中,getStaticProps
函数允许你添加一个 revalidate
字段,以在一定时间后自动重新生成页面。
在 app
目录中,使用 fetch()
进行数据获取可以使用 revalidate
,这将缓存请求指定的秒数。
API 路由
API 路由在 pages/api
目录中继续工作,无需任何更改。然而,在 app
目录中,它们已被 路由处理程序 替代。
路由处理程序允许你使用 Web Request 和 Response API 为给定路由创建自定义请求处理程序。
值得注意的是:如果你之前使用 API 路由从客户端调用外部 API,现在你可以使用 服务器组件 来安全地获取数据。了解更多关于 数据获取 的信息。
步骤 7:样式
在 pages
目录中,全局样式表仅限于 pages/_app.js
。在 app
目录中,这个限制已被取消。全局样式可以添加到任何布局、页面或组件中。
Tailwind CSS
如果你正在使用 Tailwind CSS,你需要将 app
目录添加到你的 tailwind.config.js
文件中:
你还需要在 app/layout.js
文件中导入你的全局样式:
了解更多关于 使用 Tailwind CSS 进行样式设置 的信息
代码转换工具
Next.js 提供了代码转换工具,以帮助在功能被弃用时升级你的代码库。有关更多信息,请参阅 代码转换工具。