本指南将帮助您:
最低 Node.js 版本现在是 v18.17。更多信息请参阅 Node.js 文档。
要更新到 Next.js 版本 13,请使用您偏好的包管理器运行以下命令:
npm install next@latest react@latest react-dom@latest如果您正在使用 ESLint,您需要升级您的 ESLint 版本:
npm install -D eslint-config-next@latest须知:您可能需要重启 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 路由以及新的功能和约定。新的路由在 app 目录中可用,并与 pages 目录共存。
升级到 Next.js 13 不要求使用 App 路由。您可以继续在 pages 中使用在两个目录中都有效的新功能,例如更新后的 图像组件、链接组件、脚本组件和字体优化。
<Image/> 组件Next.js 12 引入了对 Image 组件的新改进,并使用了临时导入:next/future/image。这些改进包括更少的客户端 JavaScript、更简单的图像扩展和样式设置方式、更好的可访问性以及原生浏览器懒加载。
在版本 13 中,这种新行为现在是 next/image 的默认行为。
有两个 codemods 可以帮助您迁移到新的 Image 组件:
next-image-to-legacy-image codemod:安全地自动将 next/image 导入重命名为 next/legacy/image。现有组件将保持相同的行为。next-image-experimental codemod:危险地添加内联样式并移除未使用的 props。这将改变现有组件的行为以匹配新的默认值。要使用此 codemod,您需要首先运行 next-image-to-legacy-image codemod。<Link> 组件<Link> 组件不再需要手动添加 <a> 标签作为其子元素。此行为在 版本 12.2 中作为实验性选项添加,现在已成为默认行为。在 Next.js 13 中,<Link> 始终渲染 <a> 并允许您将 props 转发到底层标签。
例如:
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 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🎥 观看: 了解如何逐步采用 App 路由 → YouTube(16 分钟)。
迁移到 App 路由可能是您第一次使用 Next.js 所基于的 React 功能,例如 Server Components、Suspense 等。当与 Next.js 的新功能(例如特殊文件和布局)结合使用时,迁移意味着要学习新的概念、心智模型和行为变化。
我们建议通过将迁移分解为更小的步骤来减少这些更新的组合复杂性。app 目录被有意设计为与 pages 目录同时工作,以允许逐页增量迁移。
app 目录支持嵌套路由和布局。了解更多。page.js 文件使路由段公开可访问。了解更多。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(路由处理器)特殊文件取代。app 目录更新到最新的 Next.js 版本(需要 13.4 或更高版本):
npm install next@latest然后,在您的项目根目录(或 src/ 目录)中创建一个新的 app 目录。
在 app 目录中创建一个新的 app/layout.tsx 文件。这是一个根布局,将应用于 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 目录必须包含一个根布局。<html> 和 <body> 标签,因为 Next.js 不会自动创建它们。pages/_app.tsx 和 pages/_document.tsx 文件。.js、.jsx 或 .tsx 扩展名。要管理 <head> HTML 元素,您可以使用内置的 SEO 支持:
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如果您有现有的 _app 或 _document 文件,您可以将其内容(例如全局样式)复制到根布局 (app/layout.tsx) 中。app/layout.tsx 中的样式不会应用于 pages/*。在迁移过程中,您应该保留 _app/_document 以防止您的 pages/* 路由出现问题。一旦完全迁移,您就可以安全地删除它们。
如果您正在使用任何 React Context 提供者,它们将需要移动到 Client Component 中。
getLayout() 模式迁移到布局(可选)Next.js 建议为 Page 组件添加一个属性,以在 pages 目录中实现按页布局。这种模式可以通过 app 目录中对嵌套布局的原生支持来取代。
之前
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>
}之后
从 pages/dashboard/index.js 中移除 Page.getLayout 属性,并按照迁移页面步骤将其迁移到 app 目录。
export default function Page() {
return <p>My Page</p>
}将 DashboardLayout 的内容移动到一个新的 Client Component 中,以保留 pages 目录的行为。
'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 DashboardLayout from './DashboardLayout'
// This is a Server Component
export default function Layout({ children }) {
return <DashboardLayout>{children}</DashboardLayout>
}您可以逐步将 DashboardLayout.js(Client Component)中非交互部分移动到 layout.js(Server Component)中,以减少发送到客户端的组件 JavaScript 数量。
next/head在 pages 目录中,next/head React 组件用于管理 <head> HTML 元素,例如 title 和 meta。在 app 目录中,next/head 被新的内置 SEO 支持所取代。
之前:
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>
</>
)
}之后:
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 '...'
}app 目录中的页面默认是 Server Components。这与 pages 目录中的页面是 Client Components 不同。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 |
我们建议将页面迁移分解为两个主要步骤:
app 目录中的新 page.js 文件中。须知:这是最简单的迁移路径,因为它与
pages目录的行为最相似。
步骤 1:创建一个新的 Client Component
app 目录中创建一个新的单独文件(即 app/home-page.tsx 或类似文件),该文件导出一个 Client Component。要定义 Client Components,请在文件顶部(在任何导入之前)添加 'use client' 指令。
pages/index.js 中默认导出的页面组件移动到 app/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:创建一个新页面
在 app 目录中创建一个新的 app/page.tsx 文件。这默认是一个 Server Component。
将 home-page.tsx Client Component 导入到页面中。
如果您在 pages/index.js 中获取数据,请使用新的数据获取 API 将数据获取逻辑直接移动到 Server Component 中。有关更多详细信息,请参阅数据获取升级指南。
// 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,则需要更新到新的路由 hook。了解更多。
启动您的开发服务器并访问 http://localhost:3000。您应该会看到您现有的索引路由,现在通过 app 目录提供服务。
已添加了一个新的路由来支持 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 hook。useSearchParams 和 usePathname 来监听页面更改。有关更多详细信息,请参阅 路由事件 部分。'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 hook 具有以下更改:
isFallback 已被移除,因为 fallback 已被替换。locale、locales、defaultLocales、domainLocales 值已移除,因为 app 目录中不再需要内置的 i18n Next.js 功能。了解有关 i18n 的更多信息。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。
pages 目录使用 getServerSideProps 和 getStaticProps 来获取页面的数据。在 app 目录中,这些旧的数据获取函数已被建立在 fetch() 和 async React Server Components 之上的更简单的 API 取代。
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)在 pages 目录中,getServerSideProps 用于在服务器上获取数据,并将 props 转发给文件中默认导出的 React 组件。页面的初始 HTML 从服务器预渲染,然后浏览器中“激活”(使其可交互)页面。
// `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 路由中,我们可以使用 Server Components 将数据获取与 React 组件并置。这使我们能够向客户端发送更少的 JavaScript,同时保留服务器渲染的 HTML。
通过将 cache 选项设置为 no-store,我们可以指示获取的数据永远不应被缓存。这与 pages 目录中的 getServerSideProps 类似。
// `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>
)
}在 pages 目录中,您可以根据 Node.js HTTP API 检索基于请求的数据。
例如,您可以从 getServerSideProps 中检索 req 对象,并用它来检索请求的 cookies 和 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 目录公开了新的只读函数来检索请求数据:
headers:基于 Web Headers API,可在 Server Components 内部用于检索请求头。cookies:基于 Web Cookies API,可在 Server Components 内部用于检索 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)在 pages 目录中,getStaticProps 函数用于在构建时预渲染页面。此函数可用于从外部 API 或直接从数据库获取数据,并将这些数据传递给在构建过程中生成的整个页面。
// `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',这将缓存请求数据直到手动失效。这与 pages 目录中的 getStaticProps 类似。
// `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)在 pages 目录中,getStaticPaths 函数用于定义在构建时应预渲染的动态路径。
// `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 取代。
generateStaticParams 的行为与 getStaticPaths 类似,但它具有简化的 API,用于返回路由参数,并且可以在布局内部使用。generateStaticParams 的返回形式是一个段数组,而不是嵌套 param 对象的数组或已解析路径的字符串。
// `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/${(await params).id}`)
const post = await res.json()
return post
}
export default async function Post({ params }) {
const post = await getPost(params)
return <PostLayout post={post} />
}使用 generateStaticParams 这个名称比 getStaticPaths 更适合 app 目录中的新模型。get 前缀被更具描述性的 generate 取代,现在 getStaticProps 和 getServerSideProps 不再必要,它单独使用起来更好。Paths 后缀被 Params 取代,这更适合具有多个动态段的嵌套路由。
fallback在 pages 目录中,从 getStaticPaths 返回的 fallback 属性用于定义在构建时未预渲染的页面的行为。此属性可以设置为 true 以在页面生成时显示回退页面,false 以显示 404 页面,或 blocking 以在请求时生成页面。
// `pages` directory
export async function getStaticPaths() {
return {
paths: [],
fallback: 'blocking'
};
}
export async function getStaticProps({ params }) {
...
}
export default function Post({ post }) {
return ...
}在 app 目录中,config.dynamicParams 属性控制如何处理 generateStaticParams 之外的参数:
true:(默认)generateStaticParams 中未包含的动态段将按需生成。false:generateStaticParams 中未包含的动态段将返回 404。这取代了 pages 目录中 getStaticPaths 的 fallback: true | false | 'blocking' 选项。fallback: 'blocking' 选项未包含在 dynamicParams 中,因为在流式传输时,'blocking' 和 true 之间的差异可以忽略不计。
// `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(默认值)时,当请求尚未生成的路由段时,它将在服务器端渲染并缓存。
getStaticProps 与 revalidate)在 pages 目录中,getStaticProps 函数允许您添加 revalidate 字段,以在一定时间后自动重新生成页面。
// `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,它将缓存请求指定的秒数。
// `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 路由在 pages/api 目录中继续工作,没有任何更改。但是,它们已被 app 目录中的 路由处理器 取代。
路由处理器允许您使用 Web Request 和 Response API 为给定路由创建自定义请求处理器。
export async function GET(request: Request) {}export async function GET(request) {}须知:如果您之前使用 API 路由从客户端调用外部 API,您现在可以改用 Server Components 来安全地获取数据。了解更多关于数据获取。
如果您同时也在从单页应用 (SPA) 迁移到 Next.js,请参阅我们的文档了解更多信息。
在 pages 目录中,全局样式表仅限于 pages/_app.js。随着 app 目录的出现,此限制已被解除。全局样式可以添加到任何布局、页面或组件中。
如果您正在使用 Tailwind CSS,您需要将 app 目录添加到您的 tailwind.config.js 文件中:
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 文件中导入您的全局样式:
import '../styles/globals.css'
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}了解更多关于使用 Tailwind CSS 进行样式设置。
在由不同的 Next.js 路由提供服务的路由之间导航时,将进行硬导航。使用 next/link 的自动链接预取不会跨路由进行预取。
相反,您可以优化 App 路由和 Pages 路由之间的导航,以保留预取和快速页面转换。了解更多。
Next.js 提供 Codemod 转换,以帮助在功能弃用时升级您的代码库。有关更多信息,请参阅 Codemods。