路由
每个应用程序的骨架都是路由。 本页面将向您介绍 Web 路由的基本概念以及如何在 Next.js 中处理路由。
术语
首先,您将看到整个文档中使用了这些术语。这是一个快速参考:
- 树:用于可视化层次结构的一种约定。例如,一个具有父组件和子组件的组件树,一个文件夹结构等。
- 子树:树的一部分,以新的根(首个)开始,以叶子(最后)结束。
- 根:树或子树中的第一个节点,例如根布局。
- 叶子:子树中没有子节点的节点,例如URL路径中的最后一段。"
- URL 段:由斜杠分隔的 URL 路径的一部分。
- URL 路径:域名后面的 URL 部分(由段组成)
app
路由
在第13版本中,Next.js引入了一个基于 React Server Components 构建的新的App Router,支持共享布局、嵌套路由、加载状态、错误处理等功能。
App Router在一个名为app
的新目录中工作。
该app
目录与pages
目录一起工作,以支持渐进式采用。
这使您可以将应用程序的某些路由选择为新行为,同时保留pages
目录中的其他路由以保持先前的行为。
如果您的应用程序使用pages
目录,
请同时查看Pages Router文档。
App Router优先于Pages Router。 跨目录的路由不应解析为相同的URL路径, 否则将导致构建时错误以防止冲突。
默认情况下,app
目录中的组件是
React Server Components。
这是一种性能优化,使您能够轻松采用它们,
您还可以使用Client Components。
如果您是Server Components的新手, 请查看Server页面。
文件夹和文件的角色
Next.js使用基于文件系统的路由器,其中:
路由段
路由中的每个文件夹表示一个路由段。每个路由段都映射到URL路径中相应的段。
嵌套路由
要创建嵌套路由,可以将文件夹嵌套在彼此内部。
例如,您可以通过在app
目录中嵌套两个新文件夹来添加一个新的/dashboard/settings
路由。
/dashboard/settings
路由由三个段组成:
/
(根段)dashboard
(段)settings
(叶段)
文件约定
Next.js提供了一组特殊文件,以在嵌套路由中创建具有特定行为的UI:
layout
:用于一个段及其子代的共享UI。page
:路由的唯一UI,使路由公开可访问。loading
:一个段及其子代的加载UI。not-found
:一个段及其子代的未找到UI。error
:一个段及其子代的错误UI。global-error
:全局错误UI。route
:服务器端API端点。template
:专门重新呈现的布局UI。default
:并行路由的回退UI。
.js
、.jsx
或.tsx
文件扩展名可用于特殊文件。
组件层次结构
路由段中特殊文件定义的React组件以特定的层次结构呈现:
layout.js
template.js
error.js
(React错误边界)loading.js
(React悬停边界)not-found.js
(React错误边界)page.js
或嵌套的layout.js
在嵌套路由中,段的组件将嵌套在其父段的组件内。
同地放置
除了特殊文件外,您还可以选择将您自己的文件(例如组件、样式、测试等)同地放置在app
目录中的文件夹中。
这是因为虽然文件夹定义路由,但只有由`page.js`或`route.js`返回的内容是公开可寻址的。
高级路由模式
App Router还提供了一组约定,帮助您实现更高级的路由模式。这些包括:
- 并行路由:允许您同时在同一视图中显示两个或更多可以独立导航的页面。您可以用于具有自己子导航的分屏视图,例如仪表板。
- 拦截路由:允许您拦截路由并在另一个路由的上下文中显示它。当保持当前页面的上下文很重要时,您可以使用这些功能。例如,在编辑一个任务时查看所有任务或在动态源中展开照片。
这些模式使您能够构建更丰富和复杂的UI,使过去对小团队和个人开发人员来说历来复杂的功能变得更加平民化。
定义路由
创建路由
Next.js使用基于文件系统的路由器,其中文件夹用于定义路由。
每个文件夹表示一个路由段,对应到一个URL段。 要创建嵌套路由,您可以将文件夹嵌套在彼此内部。
使用特殊的page.js
文件可以使路由段公开可访问。
在这个例子中,/dashboard/analytics
的URL路径是不公开可访问的,
因为它没有相应的page.js
文件。
这个文件夹可以用于存储组件、样式表、图像或其他同地放置的文件。
特殊文件可以使用.js
、.jsx
或.tsx
文件扩展名。
创建UI
使用特殊文件约定来为每个路由段创建UI。
最常见的是用于显示路由独特UI的pages
和用于显示跨多个路由共享UI的layouts
。
例如,要创建您的第一个页面,请在app
目录中添加一个page.js
文件,并导出一个React组件:
import React from 'react';
const Page: React.FC = () => {
return <h1>Hello, Next.js!</h1>;
};
export default Page;
页面和布局
Next.js 13 中的 App Router 引入了新的文件约定,可以轻松创建页面 、共享布局和模板。 本页面将指导您如何在 Next.js 应用程序中使用这些特殊文件。
页面
页面是特定路由的UI。
您可以通过从page.js
文件中导出组件来定义页面。
使用嵌套文件夹来定义路由,并使用page.js
文件使路由公开可访问。
通过在app
目录中添加page.js
文件来创建您的第一个页面:
// `app/page.tsx` is the UI for the `/` URL
export default function Page() {
return <h1>Hello, Home page!</h1>
}
// `app/dashboard/page.tsx` is the UI for the `/dashboard` URL
export default function Page() {
return <h1>Hello, Dashboard Page!</h1>
}
值得知道:
- 页面始终是路由子树的叶子。
- 可以使用
.js
、.jsx
或.tsx
文件扩展名用于页面。 - 必须使用
page.js
文件才能使路由段公开可访问。 - 页面默认是Server Components,但可以设置为Client Components。
- 页面可以获取数据。有关更多信息,请查看数据获取部分。
布局
布局是在多个页面之间共享的UI。 在导航时,布局保留状态,保持交互,并且不重新呈现。 布局也可以是嵌套的。
您可以通过从layout.js
文件中默认导出一个React组件来定义布局。
组件应接受一个children
属性,在渲 染期间将其填充为子布局(如果存在)或子页面。
export default function DashboardLayout({
children, // will be a page or nested layout
}: {
children: React.ReactNode
}) {
return (
<section>
{/* Include shared UI here e.g. a header or sidebar */}
<nav></nav>
{children}
</section>
)
}
值得知道:
- 最上层的布局称为根布局。
这是一个必需的布局,它在应用程序中的所有页面之间共享。
根布局必须包含
html
和body
标签。 - 任何路由段都可以选择性地定义自己的布局。这些布局将在该段中的所有页面之间共享。
- 路由中的布局默认是嵌套的。每个父布局使用React
children
属性包装其下方的子布局。 - 您可以使用Route Groups选择性地将特定路由段放入和移出共享布局。
- 布局默认是Server Components,但可以设置为Client Components。
- 布局可以获取数据。有关更多信息,请查看数据获取部分。
- 在父布局和其子布局之间传递数据是不可能的。 但是,您可以在路由中多次获取相同的数据,而React将自动去重请求,而不会影响性能。
- 布局无法访问其下方的路由段。要访问所有路由段,可以在Client Component中使用
useSelectedLayoutSegment
或useSelectedLayoutSegments
。 - 可以使用
.js
、.jsx
或.tsx
文件扩展名用于布局。 - 可以在同一文件夹中定义
layout.js
和page.js
文件。布局将包裹页面。
根布局(必需)
根布局在app
目录的顶层定义,并应用于所有路由。此布局使您能够修改从服务器返回的初始HTML。
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
值得知道:
app
目录必须包含一个根布局。- 根布局必须定义
<html>
和<body>
标签,因为Next.js不会自动创建它们。 - 您可以使用内置的SEO支持来管理
<head>
HTML元素,例如<title>
元素。 - 您可以使用路由组创建多个根布局。查看此处的示例。
- 根布局默认是Server Components,不能设置为Client Components。
- 从
pages
目录迁移:根布局替代了_app.js
和_document.js
文件。查看迁移指南。
嵌套布局
在文件夹中定义的布局(例如app/dashboard/layout.js
)
适用于特定的路由段(例如acme.com/dashboard
),
并在这些段处于活动状态时呈现。
默认情况下,文件层次结构中的布局是嵌套的,
这意味着它们通过其children
属性包装子布局。
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return <section>{children}</section>
}
值得知道:
- 只有根布局可以包含
<html>
和<body>
标签。
如果将上述两个布局合并,
根布局(app/layout.js
)将包装仪表板布 局(app/dashboard/layout.js
),
后者将包装app/dashboard/*
内的路由段。
这两个布局将嵌套如下:
可以使用Route Groups选择性地将特定路由段放入和移出共享布局。
模板
模板与布局相似,因为它们包装每个子布局或页面。 与布局不同的是,模板为导航中的每个子项创建一个新实例。 这意味着当用户在共享模板的路由之间导航时,该组件的新实例被挂载, DOM元素被重新创建,状态不保留,并且效果重新同步。
可能有些情况下,您需要这些特定的行为,而模板将比布局更合适。例如:
- 依赖于
useEffect
(例如记录页面视图)和useState
(例如每页反馈表单)的功能。 - 更改默认框架行为。例如,布局内的Suspense边界仅在第一次加载布局时显示回退,而在切换页面时不会显示。对于模板,每次导航都会显示回退。
可以通过从template.js
文件中导出一个默认的React组件来定义模板。组件应该接受一个children
属性。
export default function Template({ children }: { children: React.ReactNode }) {
return <div>{children}</div>
}
就嵌套而言,template.js
在布局和其子布局之间呈现。以下是一个简化的输出:
<Layout>
{/* 注意,模板被赋予唯一的键。 */}
<Template key={routeParam}>{children}</Template>
</Layout>
修改<head>
在app
目录中,您可以使用内置的SEO支持来修改<head>
HTML元素,例如标题和meta。
可以通过在layout.js
或page.js
文件中导出一个metadata
对象或
generateMetadata
函数来定义元数据。
import { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Next.js',
};
export default function Page() {
return '...';
}
您不应手动添加<head>
标签(如<title>
和<meta>
)到根布局。
相反,您应该使用Metadata API,该API会自动处理高级要求,如流式传输和去重<head>
元素。
链接和导航
在Next.js中,有两种在路由之间导航的方式:
- 使用
<Link>
组件 - 使用
useRouter
Hook
本页面将介绍如何使用 <Link>
、useRouter()
,并深入探讨导航的工作原理。
<Link>
组件
<Link>
是一个内置组件,它扩展了 HTML 的 <a>
标签,
提供了在路由之间进行预取和客户端导航的功能。
这是在Next.js中导航之间的主要方式。
您可以通过从 next/link
中导入它,并向组件传递一个 href
属性来使用它:
import Link from 'next/link'
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
还可以向 <Link>
传递其他可选的属性。更多信息请参阅 API 参考。
示例
链接到动态段
在链接到动态段时,您可以使用模板文字和插值来生成链接列表。例如,要生成博客文章列表:
import Link from 'next/link'
export default function PostList({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
</li>
))}
</ul>
)
}
检查活动链接
您可以使用 usePathname()
来确定链接是否处于活动状态。
例如,要向活动链接添加类,您可以检查当前pathname
是否与链接的 href
匹配:
'use client'
import { usePathname } from 'next/navigation'
import Link from 'next/link'
export function Links() {
const pathname = usePathname()
return (
<nav>
<ul>
<li>
<Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
Home
</Link>
</li>
<li>
<Link
className={`link ${pathname === '/about' ? 'active' : ''}`}
href="/about"
>
About
</Link>
</li>
</ul>
</nav>
)
}
滚动到id
Next.js App Router 的默认行为是在导航时滚动到新路由的顶部,或者对于后退和前进导航保持滚动位置。
如果您想要在导航时滚动到特定的标识,可以在URL后附加一个 #
锚链接,
或者只是将一个 #
锚链接传递给 href
属性。这是因为 <Link>
渲染为 <a>
元素,所以是可能的。
<Link href="/dashboard#settings">Settings</Link>
// 输出
<a href="/dashboard#settings">Settings</a>
禁用滚动恢复
Next.js App Router 的默认行为是在导航时滚动到新路由的顶部,或者对于后退和前进导航保持滚动位置。
如果您想要禁用此行为,可以将 scroll={false}
传递给 <Link>
组件,
或者将 scroll: false
传递给 router.push()
或 router.replace()
。
// next/link
<Link href="/dashboard" scroll={false}>
Dashboard
</Link>
// useRouter
import { useRouter } from 'next/navigation'
const router = useRouter()
router.push('/dashboard', { scroll: false })
useRouter()
Hook
useRouter
钩子允许您以编程方式更改路由。
此钩子只能在客户端组件内使用,并且是从 next/navigation
导入的。
'use client'
import { useRouter } from 'next/navigation'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
有关 useRouter
方法的完整列表,请参阅 API 参考。
除非有使用 useRouter
的特定需求,否则请使用 <Link>
组件在路由之间进行导航。
路由和导航的工作原理
App Router 使用混合方法进行路由和导航。 在服务器上,您的应用程序代码会自动按路由段进行代码拆分。 在客户端上,Next.js 会预取和缓存路由段。 这意味着当用户导航到新路由时,浏览器不会重新加载页面,只有更改的路由段会重新渲染, 从而提高导航体验和性能。
- 预取 预取是在用户访问之前在后台预加载路由的一种方式。
在Next.js中,有两种路由的预取方式:
<Link>
组件:路由在用户视口中变得可见时会自动进行预取。预取发生在页面首次加载时或通过滚动进入视图时。router.prefetch()
:可以使用useRouter
钩子以编程方式进行路由的预取。
对于<Link>
的预取行为在
静态和动态路由上是不同的:
- 静态路由:
prefetch
默认为true
。整个路由将被预取和缓存。 - 动态路由:
prefetch
默认为automatic
。 只有共享布局直到第一个loading.js
文件的路由段被预取和缓存,缓存有效期为30秒。 这降低了获取整个动态路由的成本,并且您可以显示即时的加载状态,以提供更好的视觉反馈。
您可以通过将 prefetch
属性设置为 false
来禁用预取。
有关更多信息,请参阅 <Link>
API 参考。
prefetch
在开发中未启用,只在生产中启用。
- 缓存 Next.js具有一个内存中的客户端端缓存, 称为Router Cache。 当用户在应用程序中导航时, 预取 路由段和访问过的路由的React Server Component Payload都存储在缓存中。
这意味着在导航时,尽可能地重用缓存,而不是向服务器发出新请求, 从而通过减少请求和数据传输的次数来提高性能。
了解有关Router Cache 如何工作以及如何配置它的更多信息。
- 部分渲染 部分渲染意味着在客户端上只重新渲染在导航时发生变化的路由段,而保留任何共享段。
例如,在两个兄弟路由之间导航,/dashboard/settings
和 /dashboard/analytics
,
将呈现settings
和analytics
,并保留共享的仪表板布局。
如果没有部分渲染,每次导航都会导致在服务器上重新渲染整个页面。 只渲染发生变化的段减少了传输的数据量和执行时间,从而提高了性能。
-
软导航 默认情况下,浏览器在页面之间执行硬导航。 这意味着浏览器重新加载页面并重置React状态,例如应用程序中的
useState
钩子和浏览器状态, 例如用户的滚动位置或焦点元素。 然而,在Next.js中,App Router 使用软导航。 这意味着React仅渲染更改的段,同时保留React和浏览器状态,而没有完全重新加载页面。 -
后退和前进导航 默认情况下,Next.js会保持后退和前进导航的滚动位置, 并在Router Cache 中重新使用路由段。
路由分组
在 app
目录中,嵌套文件夹通常映射到 URL 路径。
但是,您可以将文件夹标记为路由组,以防止将文件夹包含在路由的 URL 路径中。
这使您能够在不影响 URL 路径结构的情况下,将路由段和项目文件组织成逻辑组。
路由组对以下情况很有用:
约定
可以通过在文件夹名称周围加上括号来创建路由组:(folderName
)
示例
在不影响 URL 路径的情况下组织路由
要组织路由而不影响 URL,请创建一个组以将相关路由放在一起。
括号中的文件夹将从 URL 中省略(例如 (marketing
) 或 (shop
))。
即使 (marketing
) 和 (shop
) 中的路由共享相同的 URL 层次结构,
您仍可以通过在其文件夹中添加 layout.js
文件为每个组创建不同的布局。
将特定段投入布局
要将特定路由投入布局,请创建一个新的路由组(例如 (shop
))并将共享相同布局的路由移到该组中(例如 account
和 cart
)。
组外的路由将不共享布局(例如 checkout
)。
创建多个根布局
要创建多个根布局,请删除顶层 layout.js
文件,并在每个路由组中添加 layout.js
文件。
这对于将应用程序分区为具有完全不同 UI 或体验的部分非常有用。
需要在每个根布局中添加 <html>
和 <body>
标签。
在上面的示例中,(marketing
) 和 (shop
) 都有自己的根布局。
值得知道:
- 路由组的命名除了组织外,并没有特殊的意义。它们不影响 URL 路径。
- 包含路由组的路由不应解析为其他路由的相同 URL 路径。
例如,由于路由组不影响 URL 结构,
(marketing)/about/page.js
和(shop)/about/page.js
都将解析为/about
,并导致错误。 - 如果使用多个根布局而没有顶层
layout.js
文件,首页page.js
文件应该在路由组中定义, 例如:app/(marketing)/page.js
。 - 在多个根布局之间导航将导致完整页面加载(而不是客户端导航)。
例如,从使用
app/(shop)/layout.js
的/cart
导航到使用app/(marketing)/layout.js
的/blog
将导致完整页面加载。这仅适用于多个根布局。
动态路由
当您事先不知道确切的段名称并希望从动态数据创建路由时,可以使用动态段,在请求时填充或在构建时预渲染。
约定
可以通过将文件夹的名称用方括号括起来来创建动态段:[folderName]
。例如,[id]
或 [slug]
。
动态段作为 params
属性传递给 layout
、page
、route
和 generateMetadata
函数。
示例
例如,博客可以包含以下路由 app/blog/[slug]/page.js
,其中 [slug]
是博客文章的动态段。
export default function Page({ params }: { params: { slug: string } }) {
return <div>My Post: {params.slug}</div>;
}
Route | Example URL | params |
---|---|---|
app/blog/[slug]/page.js | /blog/a | { slug: 'a' } |
app/blog/[slug]/page.js | /blog/b | { slug: 'b' } |
app/blog/[slug]/page.js | /blog/c | { slug: 'c' } |
了解如何生成段的参数,请参阅
generateStaticParams()
页面。
动态段相当于页面目录中的动态路由。
生成静态参数
generateStaticParams
函数可以与
动态路由段
结合使用,
在构建时
静态生成
路由,而不是在请求时按需生成。
export async function generateStaticParams() {
const posts = await fetch('https://.../posts').then((res) => res.json());
return posts.map((post) => ({
slug: post.slug,
}));
}
generateStaticParams
函数的主要优势在于其智能的数据检索。
如果在 generateStaticParams
函数中使用 fetch
请求获取内容,
请求将自动进行记忆化。
这意味着在多个 generateStaticParams
、Layouts
和 Pages
之间使用相同参数进行的
fetch
请求只会执行一次,这有助于减少构建时间。
如果要从页面目录进行迁移, 请使用迁移指南。
有关更多信息和高级用法,请参阅 generateStaticParams
服务器函数文档。
捕获所有段
动态段可以通过在括号内添加省略号 [...folderName]
来扩展为捕获所有后续段。
例如,app/shop/[...slug]/page.js
将匹配 /shop/clothes
,
还将匹配 /shop/clothes/tops
、/shop/clothes/tops/t-shirts
等。
Route | Example URL | params |
---|---|---|
app/shop/[...slug]/page.js | /shop/a | { slug: ['a'] } |
app/shop/[...slug]/page.js | /shop/a/b | { slug: ['a', 'b'] } |
app/shop/[...slug]/page.js | /shop/a/b/c | { slug: ['a', 'b', 'c'] } |
可选捕获所有段
可以通过在双方括号中包含参数来使捕获所有段变为可选:[[...folderName]]
。
例如,app/shop/[[...slug]]/page.js
也将匹配 /shop
,
除了 /shop/clothes
、/shop/clothes/tops
、/shop/clothes/tops/t-shirts
之外。
捕获所有和可选捕获所有段之间的区别在于,在可选情况下,还将匹配不带参数的路由(在上述示例中为 /shop)。
Route | Example URL | params |
---|---|---|
app/shop/[[...slug]]/page.js | /shop | {} |
app/shop/[[...slug]]/page.js | /shop/a | { slug: ['a'] } |
app/shop/[[...slug]]/page.js | /shop/a/b | { slug: ['a', 'b'] } |
app/shop/[[...slug]]/page.js | /shop/a/b/c | { slug: ['a', 'b', 'c'] } |
TypeScript
在使用 TypeScript 时,可以根据配置的路由段为 params
添加类型。
export default function Page({ params }: { params: { slug: string } }) {
return <h1>My Page</h1>;
}
Route | params 类型定义 |
---|---|
app/blog/[slug]/page.js | { slug: string } |
app/shop/[...slug]/page.js | { slug: string[] } |
app/[categoryId]/[itemId]/page.js | { categoryId: string, itemId: string } |
将来 TypeScript 插件 可能会自动完成这个操作。
加载 UI 和流式处理
特殊文件 loading.js
可以帮助您使用 React Suspense
创建有意义的加载 UI。
通过这种约定,您可以在路由段的内容加载时显示
即时加载状态。
一旦渲染完成,新内容就会自动替换进来。
即时加载状态
即时加载状态是在导航后立即显示的回退 UI。 您可以预渲染加载指示器,如骨架屏和旋转器,或者未来屏幕的一个小但有意义的部分,例如封面照片、标题等。 这有助于用户了解应用正在响应并提供更好的用户体验。
通过在文件夹内添加 loading.js
文件来创建加载状态。
import LoadingSkeleton from './LoadingSkeleton';
export default function Loading() {
// 可以在 Loading 中添加任何 UI,包括骨架屏。
return <LoadingSkeleton />;
}
在同一文件夹中,loading.js
将嵌套在 layout.js
内。
它将自动将 page.js
文件和下面的任何子元素包装在 <Suspense>
边界内。
值得知道:
- 导航是立即的,即使是在以服务器为中心的路由中。
- 导航是可中断的,这意味着在完全加载路由内容之前,不需要等待更改路由以导航到另一个路由。
- 共享布局在加载新路由段时仍然可交互。
在 Next.js 中,建议使用 loading.js
约定为路由段(布局和页面),因为 Next.js 优化了这一功能。
使用 Suspense 进行流式处理
除了 loading.js
外,您还可以为自己的 UI 组件手动创建
Suspense
边界。
App Router 支持在
Node.js 和 Edge 运行时
使用 Suspense 进行流式处理。
什么是流式处理?
要了解 React 和 Next.js 中流式处理的工作原理,理解**服务器端渲染(SSR)**及其局限性是有帮助的。
使用 SSR,需要完成一系列步骤,用户才能看到并与页面交互:
- 首先,在服务器上获取给定页面的所有数据。
- 然后,服务器渲染页面的 HTML。
- 将页面的 HTML、CSS 和 JavaScript 发送到客户端。
- 使用生成的 HTML 和 CSS 显示一个非交互式用户界面。
- 最后,React 对用户界面进行水合。
这些步骤是顺序和阻塞的,这意味着服务器只能在获取所有数据之后才能渲染页面的 HTML。 并且在客户端,React 只能在下载页面中所有组件的代码之后才能进行水合。
结合 React 和 Next.js 的 SSR 通过尽快向用户显示非交互式页面来帮助提高感知加载性能。
流式处理允许您将页面的 HTML 拆分为较小的块,并逐渐将这些块从服务器发送到客户端。
这使页面的某些部分可以更早地显示,而无需等待所有数据加载完毕才能渲染任何 UI。
流式处理与 React 的组件模型配合得很好,因为可以将每个组件视为一个块。 具有较高优先级(例如产品信息)或不依赖于数据的组件(例如布局)可以首先发送,React 可以更早地开始水合。 具有较低优先级(例如评论、相关产品)的组件可以在数据获取后在同一服务器请求中发送。
当你想防止长时间的数据请求阻塞页面渲染时,流媒体尤其有用,
因为它可以缩短时间优先于字节(TTFB
)和
第一个优先绘制内容(FCP
)。
它还有助于缩短交互时间(TTI
),
尤其是在速度较慢的设备上。