Next.js - App Router Vs. Pages Router 详细对比
多年来,我们将页面放置在 Next 的“pages”目录中。
现在这种情况即将改变。
不久前,Next.js 推出了新的 App Router,显着改变了我们创建页面的方式。 但不仅是我们存储应用程序页面的目录发生了变化,而且可用的功能也发生了变化。
我们的下一个项目过去是这样的:
└── pages
├── about.js
├── index.js
└── team.js
使用 App Router,我们的应用程序的结构看起来类似于:
src/
└── app
├── about
│ └── page.js
├── globals.css
├── layout.js
├── login
│ └── page.js
├── page.js
└── team
└── route.js
创建应用程序的约定如下:
- 应用中的每个页面都有自己的目录。目录名称定义 URL 路径。
- 浏览器中访问路径时渲染的组件是 page.js。
- 我们可以将其他组件存储在路径的目录中。 如果它们没有命名为 page.js,则不会影响路由。
- 每个页面的目录中可以放置几个具有保留名称的文件。 它们都具有特定的功能。 例如,有loading.js、template.js 和layout.js——我们稍后将讨论后者。
到目前为止,一切都很好; 我们已经介绍了这些约定。
正如您所知,现有应用程序需要进行轻微的重构。 但这样做的动机是什么? 以下是 App Router 中可用的新功能。
App Router 功能介绍
Vercel 最近宣布了一些很棒的功能。 其中大多数是 App Router 独有的 - 它们不能在经典的 Pages Router 中使用。
以下是我们现在可以做的令人兴奋的事情的列表。
客户端和服务器组件 (Client and server components)
默认情况下,应用程序目录中的任何组件现在都是服务器组件。 但是,这是什么意思? 下面是一个小回顾。
服务器组件在服务器上呈现。 他们的所有代码都保留在服务器上 - 这意味着我们无法使用客户端功能,例如窗口对象或 React 中的典型钩子。 服务器组件缺乏与客户端的交互性。 当仅仅定义一个钩子时,它们甚至会失败:
客户端组件与我们在 Next.js 中使用的先前类型的组件相反且相似。 他们可以使用浏览器、提供交互性并将其 JS 代码发送到客户端。
虽然 App Router 中的所有组件默认都是服务器组件,但可以通过在文件顶部声明“使用客户端”来声明客户端组件。
这种区别仅适用于新的应用程序路由器。 以下是一个快速概述:
客户端组件:
- 浏览器API
- 事件监听器
- 所有 React 钩子
- 非常适合在客户端生成一堆 HTML
服务器组件:
- 非常适合隐藏代码和秘密
- 不要传送大部分依赖项
- 直接访问后台
- 完全集成服务器操作
布局更简单(Easier layouting)
我已经提到了layout.js 文件,它可以位于每个路径的目录中。 该组件使布局变得简单,因为路径组件会自动应用于提供的布局。 让我们看一个例子。
在我们选择的路径目录中,我们创建一个 layout.js:
// layout.js
export default function LoginLayout({ children }) {
return <div className='login-area'>{children}</div>
}
它所需要做的就是渲染一个自动传递的子组件 - 该子组件是 page.js 组件。
page.js 完全取决于我们。 由于布局是自动应用的,因此我们不需要在此文件中指定引用任何内容。
嵌套布局(Nested layouts)
新的布局文件还有更多内容。 布局组件可以应用于多个页面。 如果子目录没有单独指定布局,则使用顶级布局。
这是一个例子:
src/
└── app
├── market
│ ├── buy
│ │ └── page.js
│ ├── sell
│ │ └── page.js
│ ├── layout.js
页面“/buy”和“/sell”具有相同的布局。
服务器动作(Server actions)
目前,服务器操作仍然是一个实验性功能。
它们可以根据客户端上的事件轻松执行服务器端代码:
export default function Home() {
async function serverAction() {
'use server'
console.log('server action executed')
}
return (
<form action={serverAction}>
<button type="submit">
Call server action
</button>
</form>
)
}
按下按钮,serverAction 函数就会被执行。
这时,需要在config中启用服务器操作。
const nextConfig = {
experimental: {
serverActions: true,
},
}
拦截路由(Intercepting routes)
拦截路由顾名思义:拦截路由请求。 此功能使我们能够构建根据其他一些因素表现不同的页面。
Vercel 自己实现了一个令人兴奋的示例:https://nextgram.vercel.app/
当您单击图库中的图像时,它会作为模式打开 - 而且路径也会发生变化,从而寻址到确切的图像。
如果刷新页面,它将呈现为独立页面。 然而,道路并没有改变。 由于路由被拦截,我们对同一路径收到不同的行为:
并行路由(Parallel routing)
平行路线与拦截路线结合使用时最为有效。 并行路由功能本身听起来并不太令人兴奋:
并行路由允许您同时或有条件地在同一布局中渲染一个或多个页面
- 官方文档
该功能非常适合组合两个或多个可以独立查看的页面。 这些页面仍然是独立的 - 因此,即使在同一 URL 上呈现,它们也具有单独的代码。
页面路由器(Pages Router)
页面与应用程序路由器并不是一场平等的争论。 相反,应用程序路由器是下一步,并且应该成为构建应用程序的标准方式。
一些受人喜爱的功能与 Pages Router 绑定在一起,因此不会占上风:
- getStaticProps
- getServerSideProps
- getStaticPaths
- Custom document component(自定义文档组件)
- Custom app component(自定义应用程序组件)
- next/head
好消息是这两条路线可以同时使用。 Vercel 甚至建议在迁移时保留页面路由器,以免破坏任何内容。
我们的自定义文档和应用程序组件可以替换为根布局。
以 SEO 为中心的 next/head 组件是完全多余的。 新的应用程序路由器提供了对元数据的内置支持,如下所示:
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Hello World',
}
export default function Page() {
return <></>
}
函数 getStaticProps
、getServerSideProps
和 getStaticPaths
可以被新的服务器动作(Server Actions)替换。
性能(Performance)
当谈到页面与应用程序路由器的性能之争时,我们看到了一些有趣的事情。 应用程序路由器的性能较差。
为了进行简单的基准测试,我创建了两个执行相同操作的项目:渲染由 URL 传递的许多 HTML 标签。
结果如下:
特别是对于 100 个和 1000 个呈现的标签,百分比差异相当大(App Router 的性能低)。 令人着迷的是,随着标签的增多,两种表现似乎变得更加接近。
该基准测试的灵感来自 Jack Herrington,我建议您查看他的深入性能视频。
https://youtu.be/3Q2q2gs0nAI
总结(Summary)
久而久之,我们就要和新路由器交朋友了。
虽然从页面过渡到新方法需要一些工作,但新功能是值得的。 然而,如果您对其功能感到满意,现有的路由器可能仍然是新项目的解决方案。
对于潜在的弃用,我并没有压力。 对页面路由器的支持可能始终存在,因为 Next.js 已经为这两个模型提供了两份单独的文档。 然而,新功能可能会成为应用程序路由器独有的。
感谢您的阅读!
有关新 App Router 及其功能的更多信息:
https://louispetrik.medium.com/what-to-watch-out-for-when-using-nexts-new-server-actions-ffae2262b12a