Skip to main content

App Router 增量采用指南

本指南将帮助你:

¥This guide will help you:

升级

¥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 for ESLint: Restart ESLint Server.

下一步

¥Next Steps

更新后,请参阅以下部分了解后续步骤:

¥After you've updated, see the following sections for next steps:

升级新功能

¥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 renames next/image imports to next/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 the next-image-to-legacy-image codemod first.

¥<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 的行为已更新以支持 pagesapp,但需要进行一些更改以确保顺利迁移:

¥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 in app and scripts denoted with this strategy will either have to be removed or modified to use a different strategy (e.g. lazyOnload).

  • onLoadonReadyonError 处理程序在服务器组件中不起作用,因此请确保将它们移至 客户端组件 或完全删除它们。

    ¥onLoad, onReady, and onError 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 模块,使你能够自定义字体加载体验,同时仍然确保出色的性能和隐私。pagesapp 目录均支持 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.

虽然 内联 CSSpages 中仍然有效,但在 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.jslayout.js

    ¥Special file conventions are used to create UI for each route segment. The most common special files are page.js and layout.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 中的 getServerSidePropsgetStaticProps 等数据获取函数已被 一个新的 API 替换。getStaticPaths 已替换为 generateStaticParams

    ¥Data fetching functions like getServerSideProps and getStaticProps have been replaced with a new API inside app. getStaticPaths has been replaced with generateStaticParams.

  • pages/_app.jspages/_document.js 已替换为单个 app/layout.js 根布局。了解更多

    ¥pages/_app.js and pages/_document.js have been replaced with a single app/layout.js root layout. Learn more.

  • pages/_error.js 已替换为更细粒度的 error.js 特殊文件。了解更多

    ¥pages/_error.js has been replaced with more granular error.js special files. Learn more.

  • pages/404.js 已替换为 not-found.js 文件。

    ¥pages/404.js has been replaced with the not-found.js file.

  • pages/api/* API 路由已替换为 route.js(路由处理程序)特殊文件。

    ¥pages/api/* API Routes have been replaced with the route.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.tsxpages/_document.tsx 文件。

    ¥The root layout replaces the pages/_app.tsx and pages/_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 { 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 from pages/dashboard/index.js and follow the steps for migrating pages to the app directory.

    export default function Page() {
    return <p>My Page</p>
    }
  • DashboardLayout 的内容移动到新的 客户端组件 中以保留 pages 目录行为。

    ¥Move the contents of DashboardLayout into a new Client Component to retain pages 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 new layout.js file inside the app 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) into layout.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 元素,例如 titlemeta 。 在 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 { 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 '...'
}

查看所有元数据选项

¥See all metadata options.

步骤 4:迁移页面

¥Step 4: Migrating Pages

  • app 目录 中的页面默认为 服务器组件。这与 pages 目录不同,其中页面为 客户端组件

    ¥Pages in the app directory are Server Components by default. This is different from the pages directory where pages are Client Components.

  • 数据获取app 中发生了变化。getServerSidePropsgetStaticPropsgetInitialProps 已替换为更简单的 API。

    ¥Data fetching has changed in app. getServerSideProps, getStaticProps and getInitialProps have been replaced with a simpler API.

  • app 目录使用 定义路由 的嵌套文件夹和特殊的 page.js 文件来使路由段可公开访问。

    ¥The app directory uses nested folders to define routes and a special page.js file to make a route segment publicly accessible.

  • pages 目录app 目录路由
    index.jspage.js/
    about.jsabout/page.js/about
    blog/[slug].jsblog/[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 the app 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 to 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:创建一个新页面

¥Step 2: Create a new page

  • app 目录中创建一个新的 app/page.tsx 文件。默认情况下这是一个服务器组件。

    ¥Create a new app/page.tsx file inside the app 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 from next/navigation and has different behavior to the useRouter hook in pages which is imported from next/router.

  • 新的 useRouter 不返回 pathname 字符串。请改用单独的 usePathname 钩子。

    ¥The new useRouter does not return the pathname string. Use the separate usePathname hook instead.

  • 新的 useRouter 不返回 query 对象。请改用单独的 useSearchParams 钩子。

    ¥The new useRouter does not return the query object. Use the separate useSearchParams hook instead.

  • 你可以使用 useSearchParamsusePathname 一起监听页面变化。有关详细信息,请参阅 路由事件 部分。

    ¥You can use useSearchParams and usePathname 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 because fallback has been replaced.

  • localelocalesdefaultLocalesdomainLocales 值已被删除,因为 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 the app directory. Learn more about i18n.

  • basePath 已被删除。替代方案不会成为 useRouter 的一部分。目前尚未实现。

    ¥basePath has been removed. The alternative will not be part of useRouter. It has not yet been implemented.

  • asPath 已被删除,因为 as 的概念已从新路由中删除。

    ¥asPath has been removed because the concept of as 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 the useSearchParams() hook will skip the prerendering step and instead be rendered on the client at runtime.

查看 useRouter() API 参考

¥View the useRouter() API reference.

步骤 6:迁移数据获取方法

¥Step 6: Migrating Data Fetching Methods

pages 目录使用 getServerSidePropsgetStaticProps 来获取页面数据。在 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 目录中,我们可以使用 服务器组件 将数据获取放在 React 组件中。这使我们能够向客户端发送更少的 JavaScript,同时保留服务器渲染的 HTML。

¥In the app directory, 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:

// `app` directory
import { cookies, headers } from 'next/headers'

async function getData() {
const authHeader = 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 = cookies().get('theme')
const data = await getData()
return '...'
}
// `app` directory
import { cookies, headers } from 'next/headers'

async function getData() {
const authHeader = 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 = 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 目录中的新模型,使用名称 generateStaticParamsgetStaticPaths 更合适。get 前缀被替换为更具描述性的 generate,现在不再需要 getStaticPropsgetServerSideProps,单独使用它会更好。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 in generateStaticParams are generated on demand.

  • false:未包含在 generateStaticParams 中的动态段将返回 404。

    ¥**false**: Dynamic segments not included in generateStaticParams will return a 404.

这替换了 pages 目录中 getStaticPathsfallback: 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.

增量静态再生(getStaticPropsrevalidate

¥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() 获取数据可以使用 revalidaterevalidate 会将请求缓存指定的秒数。

¥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.