应用路由渐进式迁移指南
本指南将帮助您:
升级步骤
Node.js 版本
最低 Node.js 版本要求现已提升至 v18.17。更多信息请参阅 Node.js 文档。
Next.js 版本
要升级至 Next.js 13 版本,请使用您偏好的包管理器运行以下命令:
ESLint 版本
如果使用 ESLint,需要升级 ESLint 版本:
须知:您可能需要重启 VS Code 中的 ESLint 服务器才能使更改生效。打开命令面板(Mac 使用
cmd+shift+p
;Windows 使用ctrl+shift+p
)并搜索ESLint: 重启 ESLint 服务器
。
后续步骤
完成升级后,请参考以下章节继续操作:
- 升级新功能:指导您升级至改进后的 Image 和 Link 组件等新功能。
- 从
pages
迁移至app
目录:逐步指导您从pages
渐进式迁移到app
目录。
升级新功能
Next.js 13 引入了全新的 应用路由 (App Router),包含新特性和约定。新路由在 app
目录中可用,并与 pages
目录共存。
升级至 Next.js 13 不强制要求使用新的 应用路由 (App Router)。您可以继续使用 pages
目录,同时享受适用于两个目录的新功能,例如更新后的 Image 组件、Link 组件、Script 组件 和 字体优化。
<Image/>
组件
Next.js 12 通过临时导入 next/future/image
为 Image 组件引入了多项改进,包括减少客户端 JavaScript、更简便的图像扩展与样式设置、更好的可访问性以及原生浏览器懒加载。
在版本 13 中,这些新行为已成为 next/image
的默认功能。
我们提供了两个代码修改工具 (codemod) 来帮助迁移至新版 Image 组件:
next-image-to-legacy-image
codemod:安全自动地将next/image
导入重命名为next/legacy/image
,现有组件将保持相同行为。next-image-experimental
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
处理程序在服务端组件 (Server Components) 中无效,请确保将它们移至 客户端组件 (Client Component) 或完全移除。
字体优化
此前,Next.js 通过 内联字体 CSS 帮助优化字体。版本 13 引入了新的 next/font
模块,让您能自定义字体加载体验,同时确保卓越性能和隐私保护。next/font
同时支持 pages
和 app
目录。
虽然 内联 CSS 在 pages
中仍然有效,但在 app
中不可用。您应改用 next/font
。
参阅 字体优化 页面了解如何使用 next/font
。
从 pages
迁移至 app
🎥 观看视频:学习如何渐进式采用应用路由 → YouTube (16分钟)。
迁移至应用路由可能是首次使用 React 特性(如服务端组件 (Server Components)、Suspense 等)与 Next.js 新功能(如 特殊文件 和 布局 (Layouts))的结合。这意味着需要学习新的概念、思维模型和行为变化。
我们建议通过将迁移分解为小步骤来降低整体复杂度。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
文件。这是 根布局 (root layout),将应用于 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 提供程序,需要将它们移至 客户端组件 (Client Component)。
将 getLayout()
模式迁移至布局 (可选)
Next.js 曾建议 向页面组件添加属性 以在 pages
目录中实现每页布局。该模式可被 app
目录中对 嵌套布局 (nested layouts) 的原生支持取代。
查看前后示例
迁移前
迁移后
-
从
pages/dashboard/index.js
中移除Page.getLayout
属性,并按照 迁移页面的步骤 将页面迁移至app
目录。app/dashboard/page.js -
将
DashboardLayout
的内容移至新的 客户端组件 (Client Component) 以保留pages
目录的行为。app/dashboard/DashboardLayout.js -
将
DashboardLayout
导入到app
目录中的新layout.js
文件。app/dashboard/layout.js -
您可以逐步将
DashboardLayout.js
(客户端组件)中的非交互部分移至layout.js
(服务端组件),以减少发送至客户端的组件 JavaScript 量。
步骤 3:迁移 next/head
在 pages
目录中,next/head
React 组件用于管理 <head>
HTML 元素如 title
和 meta
。在 app
目录中,next/head
被新的 内置 SEO 支持 取代。
迁移前:
迁移后:
步骤 4:迁移页面
app
目录中的页面默认是服务端组件 (Server Components)。这与pages
目录不同,后者中的页面是客户端组件 (Client Components)。app
目录中的数据获取 (Data fetching) 方式已变更。getServerSideProps
、getStaticProps
和getInitialProps
已被更简洁的 API 取代。app
目录使用嵌套文件夹来定义路由 (define routes),并通过特殊的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
,则需要更新为新的路由钩子。了解更多。 -
启动开发服务器并访问
http://localhost:3000
。你应该能看到现有的索引路由现在通过app
目录提供服务。
步骤 5:迁移路由钩子
新增了一个路由器以支持 app
目录中的新行为。
在 app
目录中,应使用从 next/navigation
导入的三个新钩子:useRouter()
、usePathname()
和 useSearchParams()
。
- 新的
useRouter
钩子从next/navigation
导入,其行为与从next/router
导入的pages
中的useRouter
钩子不同。- 从
next/router
导入的useRouter
钩子 在app
目录中不受支持,但可以继续在pages
目录中使用。
- 从
- 新的
useRouter
不返回pathname
字符串。请改用单独的usePathname
钩子。 - 新的
useRouter
不返回query
对象。请改用单独的useSearchParams
钩子。 - 可以同时使用
useSearchParams
和usePathname
来监听页面变化。更多详情请参阅路由事件 (Router Events) 部分。 - 这些新钩子仅在客户端组件中受支持,不能在服务端组件中使用。
此外,新的 useRouter
钩子还有以下变更:
isFallback
已被移除,因为fallback
已被替换。locale
、locales
、defaultLocales
和domainLocales
值已被移除,因为app
目录不再需要 Next.js 的内置国际化 (i18n) 功能。了解更多关于国际化 (i18n)。basePath
已被移除。替代方案将不属于useRouter
,目前尚未实现。asPath
已被移除,因为新路由器中移除了as
的概念。isReady
已被移除,因为它不再必要。在静态渲染 (static rendering) 期间,任何使用useSearchParams()
钩子的组件将跳过预渲染步骤,改为在运行时在客户端渲染。
步骤 6:迁移数据获取方法
pages
目录使用 getServerSideProps
和 getStaticProps
来为页面获取数据。在 app
目录中,这些旧的数据获取函数被替换为基于 fetch()
和异步 React 服务端组件的更简洁 API。
服务端渲染 (getServerSideProps
)
在 pages
目录中,getServerSideProps
用于在服务器端获取数据并将 props 传递给文件中的默认导出 React 组件。页面的初始 HTML 由服务器预渲染,随后在浏览器中“水合”(hydrating) 页面(使其可交互)。
在 app
目录中,我们可以使用服务端组件 (Server Components) 将数据获取逻辑与 React 组件放在一起。这样可以在保持服务器渲染 HTML 的同时,减少发送到客户端的 JavaScript。
通过将 cache
选项设置为 no-store
,我们可以指示获取的数据不应缓存。这与 pages
目录中的 getServerSideProps
类似。
访问请求对象
在 pages
目录中,可以基于 Node.js HTTP API 获取与请求相关的数据。
例如,可以从 getServerSideProps
中获取 req
对象,并使用它来获取请求的 cookies 和 headers。
app
目录提供了新的只读函数来获取请求数据:
headers()
:基于 Web Headers API,可在服务端组件 (Server Components) 中使用以获取请求头。cookies()
:基于 Web Cookies API,可在服务端组件 (Server Components) 中使用以获取 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
,更适用于具有多个动态段 (dynamic segments) 的嵌套路由。
替换 fallback
在 pages
目录中,getStaticPaths
返回的 fallback
属性用于定义未在构建时预渲染的页面的行为。该属性可设置为 true
以在页面生成时显示回退页面,false
显示 404 页面,或 blocking
在请求时生成页面。
在 app
目录中,config.dynamicParams
属性 控制如何处理 generateStaticParams
之外的参数:
true
:(默认值)未包含在generateStaticParams
中的动态段 (dynamic segments) 会按需生成。false
:未包含在generateStaticParams
中的动态段将返回 404。
这替代了 pages
目录中 getStaticPaths
的 fallback: true | false | 'blocking'
选项。fallback: 'blocking'
选项未包含在 dynamicParams
中,因为在流式渲染 (streaming) 下 'blocking'
和 true
的差异可以忽略不计。
当 dynamicParams
设置为 true
(默认值)时,如果请求的路由段尚未生成,它将在服务端渲染并被缓存。
增量静态再生 (getStaticProps
与 revalidate
)
在 pages
目录中,getStaticProps
函数允许添加 revalidate
字段,以在一定时间后自动重新生成页面。
在 app
目录中,使用 fetch()
获取数据时可以指定 revalidate
,这将缓存请求指定的秒数。
API 路由
API 路由在 pages/api
目录中继续正常工作,无需更改。但它们已被 app
目录中的 路由处理器 (Route Handlers) 替代。
路由处理器允许使用 Web 的 Request 和 Response API 为指定路由创建自定义请求处理器。
须知:如果您之前使用 API 路由从客户端调用外部 API,现在可以使用 服务端组件 (Server Components) 安全地获取数据。了解更多关于 数据获取 的内容。
步骤 7:样式
在 pages
目录中,全局样式表仅限于 pages/_app.js
。在 app
目录中,此限制已被取消。全局样式可以添加到任何布局、页面或组件中。
Tailwind CSS
如果您使用 Tailwind CSS,需要在 tailwind.config.js
文件中添加 app
目录:
还需在 app/layout.js
文件中导入全局样式:
了解更多关于 使用 Tailwind CSS 进行样式设计
代码修改工具 (Codemods)
Next.js 提供了代码修改工具 (Codemod) 转换功能,帮助您在功能弃用时升级代码库。更多信息请参阅 代码修改工具 (Codemods)。