Next.js 13.4 是 Next.js 的一个基础版本发布,标志着应用App Router的稳定性。
- React Server Components: 使用 React Server Components 可以更有效地构建和渲染组件,提供更好的性能和开发体验。
- Nested Routes & Layouts: 嵌套路由和布局,现在可以更轻松地创建嵌套路由和布局,帮助构建复杂的页面结构。
- Simplified Data Fetching: 简化的数据获取,数据获取过程现在更加简单,有助于提高页面加载速度和开发效率。
- Streaming & Suspense: 流式加载和 Suspense,引入流式加载和 Suspense 机制,让页面在加载数据时更加流畅和高效。
- Built-in SEO Support: 内置 SEO 支持,Next.js 现在内置了对搜索引擎优化 (SEO) 的支持,有助于提高应用程序在搜索引擎中的排名。
- Turbopack (Beta) : Turbopack 是一个本地开发服务器,具有更快的性能和更好的稳定性,可以加速开发流程;
- Server Actions (Alpha) : Server Actions 允许在服务器上修改数据,无需使用客户端 JavaScript,为一些特定场景提供了更便捷的解决方案;
自从六个月前发布 Next.js 13 以来,我们一直致力于构建 Next.js 未来的基础——App Router,以一种可以逐步采用而不会造成不必要的破坏性变化的方式。
现在,随着 13.4 版本的发布,你可以开始在生产环境中逐步采用 App Router 了。
npm i next@latest react@latest react-dom@latest eslint-config-next@latest
Next.js App Router
Next.js于2016年发布,旨在为React应用程序提供一种简便的服务器端渲染方式,我们的目标是创建一个更加动态、个性化和全球化的Web。
在最初的发布公告中,我们分享了一些Next.js的设计原则:
- 零配置:将文件系统作为API使用。
- 仅使用JavaScript:一切皆为函数。
- 自动服务器端渲染和代码拆分。
- 数据获取由开发人员负责。
Next.js如今已经六岁了。我们最初的设计原则一直保持不变,并且随着更多的开发人员和公司采用Next.js,我们一直在对框架进行基础性的升级,以更好地实现这些原则。
我们一直在致力于开发Next.js的下一代,而现在,通过13.4版本,这一下一代版本已经稳定并且可以开始使用了。这篇文章将更多地介绍我们在App Router方面的设计决策和选择。
零配置:将文件系统作为API使用
基于文件系统的路由是 Next.js 的核心特性。在我们最初的发布公告中,我们展示了一个通过单个 React 组件创建路由的示例:
// pages/about.js
// Pages Router
import React from 'react';
export default () => <h1>About us</h1>;
在最初的版本中,不需要任何额外的配置。只需在 pages/
目录下放入一个文件,Next.js 路由器将会处理其余的工作。我们仍然喜欢这种简单的路由方式。但随着框架的使用增加,开发人员希望用它构建更多类型的界面。
开发人员要求更好地支持定义布局,将 UI 的各个部分作为布局进行嵌套,并对加载和错误状态有更多的灵活性。然而,将这些功能无缝融入现有的 Next.js 路由器并不容易。
框架的每个部分都必须围绕路由器进行设计,包括页面过渡、数据获取、缓存、数据修改和重新验证、流式加载、内容样式等等。
为了使我们的路由器兼容流式加载,并解决对布局增强支持的需求,我们着手构建了新版本的路由器。
在我们最初发布 布局 RFC后,我们最终确定了以下方案。
// New: App Router ✨
// app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
// app/page.js
export default function Page() {
return <h1>Hello, Next.js!</h1>;
}
比你在这里看到的更重要的是你看不到的部分。这个新的路由器(可以通过 app/
目录逐步采用)具有完全不同的架构,建立在 React Server Components 和 Suspense 的基础之上。
这个基础让我们能够移除 Next.js 特定的 API,这些 API 最初是为了扩展 React 的基本功能而开发的。例如,你不再需要使用自定义的 _app
文件来自定义全局共享布局:
pages/_app.js
// Pages Router
// This "global layout" wraps all routes. There's no way to
// compose other layout components, and you cannot fetch global
// data from this file.
export default function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />;
}
在 Pages Router 中,布局无法进行组合,并且数据获取无法与组件共存。但是在新的 App Router 中,这些问题得到了支持和改进。
app/layout.js
// New: App Router ✨
// The root layout is shared for the entire application
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
app/dashboard/layout.js
// Layouts can be nested and composed
export default function DashboardLayout({ children }) {
return (
<section>
<h1>Dashboard</h1>
{children}
</section>
);
}
在旧的 Pages Router 中,我们使用 _document
文件来自定义从服务器传输到客户端的初始页面内容(initial payload)。
pages/_document.js
// Pages Router
// This file allows you to customize the <html> and <body> tags
// for the server request, but adds framework-specific features
// rather than writing HTML elements.
import { Html, Head, Main, NextScript } from 'next/document';
export default function Document() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
使用 App Router,你不再需要从 Next.js 中导入<Html>
<Head>
和 <Body>
组件。相反,你只需要使用React组件。
app/layout.js
// New: App Router ✨
// The root layout is shared for the entire application
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
构建新的文件系统路由器的机会也是解决许多其他与路由系统相关的功能请求的合适时机。例如:
- 以前,你只能在
_app.js
中从外部 npm 包(如组件库)导入全局样式表。这对开发者来说并不是最理想的体验。有了 App Router,你可以在任何组件中导入(并放置在相同位置)任何 CSS 文件。 - 以前,在 Next.js 中启用服务器端渲染(通过
getServerSideProps
)意味着在整个页面完全加载之前,与应用程序的交互会被阻塞。但是通过 App Router,我们对架构进行了重构,使其与 React Suspense 深度集成,这意味着我们可以选择性地部分加载页面,而不会阻塞 UI 中的其他组件的交互。内容可以直接从服务器流式传输,提高页面的感知加载性能。 - 路由器是 Next.js 的核心,但它不仅仅关乎路由器本身,而是关乎如何将框架的其他组件整合在一起,比如数据获取。
通过引入新的 App Router,我们不仅解决了旧版本中的一些限制和不足,还使得整个框架的不同组件能更好地协同工作。例如,与 React Suspense 的结合让页面的加载性能得到提升,同时还增强了样式和数据获取方面的灵活性。这使得 Next.js 在构建现代动态 Web 应用程序方面变得更加强大和全面。
仅使用JavaScript:一切皆为函数
Next.js 和 React 开发者希望能够编写 JavaScript 和 TypeScript 代码,并将应用程序组件进行组合。从我们最初的发布公告中:
pages/index.js
import React from 'react';
import Head from 'next/head';
export default () => (
<div>
<Head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<h1>Hi. I'm mobile-ready!</h1>
</div>
);
在未来的 Next.js 版本中,我们增加了一个开发者体验 (DX) 的改进,自动为你导入 React。
这个组件封装了可在应用程序中任何地方重复使用和组合的逻辑。结合文件系统路由,这意味着你可以轻松地开始构建感觉像编写 JavaScript 和 HTML 的 React 应用程序。
例如,如果你想获取一些数据,最初版本的 Next.js 代码看起来是这样的:
pages/index.js
import React from 'react';
import 'isomorphic-fetch';
export default class extends React.Component {
static async getInitialProps() {
const res = await fetch('https://api.company.com/user/123');
const data = await res.json();
return { username: data.profile.username };
}
}
在未来的 Next.js 版本中,我们增加了一个开发者体验 (DX) 改进,对 fetch 进行了 polyfill 处理,这样你就不需要再导入
isomorphic-fetch
或node-fetch
,而且可以在客户端和服务器上都使用 Webfetch API
。
随着 Next.js 的广泛应用和框架的成熟,我们开始探索新的数据获取模式。
getInitialProps
在服务器端和客户端都会执行。这个 API 扩展了 React 组件,允许你返回一个 Promise
,并将结果传递给组件的 props
。
虽然 getInitialProps
目前仍然有效,但根据客户反馈,我们继续在下一代数据获取 API 上进行迭代:getServerSideProps
和 getStaticProps
。
pages/index.js
// Generate a static version of the route
export async function getStaticProps(context) {
return { props: {} };
}
// Or dynamically server-render the route
export async function getServerSideProps(context) {
return { props: {} };
}
这些 API 更清楚地标明了你的代码在何处运行,是在客户端还是服务器端,并允许 Next.js 应用程序自动进行静态优化。此外,它还支持静态导出,使 Next.js 能够部署到不支持服务器的地方(例如 AWS S3 存储桶)。
然而,这些特性并不仅仅是“纯粹的 JavaScript”,而我们希望更贴近我们最初的设计原则。
自从创建 Next.js 以来,我们与 React 核心团队(Meta)紧密合作,在 React 原语的基础上构建框架特性。我们的合作伙伴关系,加上 React 核心团队多年的研发经验,为 Next.js 提供了通过最新版本的 React 架构(包括 Server Components)实现我们目标的机会。
使用 App Router,你可以使用熟悉的 async 和 await 语法来获取数据,无需学习新的 API。默认情况下,所有组件都是 React Server Components,因此数据获取安全地在服务器端进行。
app/page.js
export default async function Page() {
const res = await fetch('https://api.example.com/...');
// The return value is *not* serialized
// You can use Date, Map, Set, etc.
const data = res.json();
return '...';
}
至关重要的是,“数据获取由开发者决定”的原则得以实现。你可以获取数据并组合任何组件。不仅限于第一方组件,还包括在 Server Components 生态系统中的任何组件,比如 Twitter 嵌入的 react-tweet 组件,它已经被设计成与 Server Components 集成,并完全在服务器上运行。
app/page.js
import { Tweet } from 'react-tweet';
export default async function Page() {
return <Tweet id="790942692909916160" />;
}
由于路由器与 React Suspense 集成,你可以更流畅地展示在内容加载时的回退内容,并在需要时逐步显示内容。
app/page.js
import { Suspense } from 'react';
import { PostFeed, Weather } from './components';
export default function Page() {
return (
<section>
<Suspense fallback={<p>Loading feed...</p>}>
<PostFeed />
</Suspense>
<Suspense fallback={<p>Loading weather...</p>}>
<Weather />
</Suspense>
</section>
);
}
此外,路由器将页面导航标记为过渡,从而使路由过渡可被中断。
自动服务器端渲染和代码拆分
在创建 Next.js 的时候,开发者通常需要手动配置 webpack、babel 和其他工具,以使 React 应用程序运行起来。而进一步的优化,例如服务器端渲染或代码分割,往往在自定义解决方案中并未实现。因此,Next.js 和其他 React 框架提供了一个抽象层来实现和强制这些最佳实践。
基于路由的代码分割意味着 pages/
目录中的每个文件将被拆分成独立的 JavaScript bundle,有助于减少文件系统的负担并提高初始页面加载性能。
这对于服务器端渲染的应用程序以及使用 Next.js 的单页应用程序都是有益的,因为后者在应用程序启动时通常会加载一个较大的单一 JavaScript bundle。然而,要实现组件级别的代码分割,开发者需要使用 next/dynamic
来动态地导入组件。
app/page.tsx
import dynamic from 'next/dynamic';
const DynamicHeader = dynamic(() => import('../components/header'), {
loading: () => <p>Loading...</p>,
});
export default function Home() {
return <DynamicHeader />;
}
在 App Router 中,Server Components 不会被包含在用于浏览器的 JavaScript bundle 中。客户端组件默认情况下会自动进行代码拆分,使用 webpack 或 Next.js 中的 Turbopack。此外,由于整个路由器架构支持流式传输和 React Suspense,你可以逐步将 UI 的部分内容从服务器发送到客户端。
例如,你可以通过条件逻辑对整个代码路径进行代码拆分。在这个例子中,对于已注销的用户,你不需要加载仪表盘的客户端 JavaScript。
app/layout.tsx
import { getUser } from './auth';
import { Dashboard, Landing } from './components';
export default async function Layout() {
const isLoggedIn = await getUser();
return isLoggedIn ? <Dashboard /> : <Landing />;
}
Turbopack (Beta)
"Turbopack"(测试版)是我们正在通过 Next.js 进行测试和稳定的新打包工具,它有助于加快在 Next.js 应用程序上进行本地迭代开发(通过 next dev --turbo),并且即将支持生产构建(next build --turbo)。
自 Next.js 13 的 alpha 版本发布以来,我们看到了越来越多的用户开始使用 Turbopack,并在此过程中修复了一些错误并添加了对缺失功能的支持。我们在 Vercel.com 上使用 Turbopack,并与许多在大型 Next.js 网站上运行的 Vercel 客户一起收集反馈并改进其稳定性。我们非常感谢社区对我们团队进行测试和报告错误的支持。
现在,六个月过去了,我们准备进入 beta 阶段。
Turbopack 与 webpack 和 Next.js 目前还没有完全的功能对等。我们正在通过这个问题追踪对这些功能的支持。然而,绝大多数用例现在应该都得到了支持。我们在这个 beta 版本中的目标是继续解决由于广泛采用而产生的遗留问题,并为未来版本的稳定性做好准备。
我们致力于改进 Turbopack 的增量引擎和缓存层,这不仅将加速本地开发,而且很快也将加速生产构建。请期待未来的 Next.js 版本,届时你将能够运行 next build --turbo 来进行即时构建。
在 Next.js 13.4 中尝试 Turbopack 的 beta 版本,只需使用 next dev --turbo 命令即可。
Server Actions (Alpha)
React 生态系统在表单、管理表单状态、以及缓存和重新验证数据方面进行了许多创新和探索。随着时间的推移,React 对其中一些模式变得更加偏爱。例如,推荐使用“不受控组件”来管理表单状态。
目前的解决方案生态系统要么是可重用的客户端解决方案,要么是嵌入到框架中的基本功能。直到现在,还没有一种方法可以组合服务器变异和数据基元。React 团队一直在开发一种用于变异的第一方解决方案。
我们很高兴地宣布 Next.js 支持实验性的 Server Actions,使你能够在服务器上对数据进行变异,直接调用函数而无需创建中间 API 层。
app/post/[id]/page.tsx (Server Component)
import kv from './kv';
export default function Page({ params }) {
async function increment() {
'use server';
await kv.incr(`post:id:${params.id}`);
}
return (
<form action={increment}>
<button type="submit">Like</button>
</form>
);
}
通过 Server Actions,你可以拥有强大的服务器优先的数据变异功能,减少客户端 JavaScript 的使用,并实现逐步增强的表单。
app/post/new/page.tsx (Server Component)
import db from './db';
import { redirect } from 'next/navigation';
async function create(formData: FormData) {
'use server';
const post = await db.post.insert({
title: formData.get('title'),
content: formData.get('content'),
});
redirect(`/blog/${post.slug}`);
}
export default function Page() {
return (
<form action={create}>
<input type="text" name="title" />
<textarea name="content" />
<button type="submit">Submit</button>
</form>
);
}
Next.js 中的 Server Actions 已经被设计为与其他数据生命周期深度集成,包括 Next.js 缓存、增量静态再生成(ISR)和客户端路由器。
通过新的 revalidatePath 和 revalidateTag API 对数据进行重新验证意味着在一次网络往返中可以完成数据变更、页面重新渲染或重定向等操作,确保在客户端正确显示数据,即使上游提供者响应较慢。
app/dashboard/posts/page.tsx (Server Component)
import db from './db';
import { revalidateTag } from 'next/cache';
async function update(formData: FormData) {
'use server';
await db.post.update({
title: formData.get('title'),
});
revalidateTag('posts');
}
export default async function Page() {
const res = await fetch('https://...', { next: { tags: ['posts'] } });
const data = await res.json();
// ...
}
Server Actions 被设计为可组合的。React 社区的任何人都可以构建、发布和分发 Server Actions 到生态系统中。就像 Server Components 一样,我们对客户端和服务器端的可组合原语的新时代感到兴奋。
Server Actions 今天在 Next.js 13.4 中的 alpha 版本中可用。通过选择使用 Server Actions,Next.js 将使用 React 的实验性版本通道。
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverActions: true,
},
};
module.exports = nextConfig;
Other Improvements
Draft Mode(草稿模式):从你的 headless CMS 中获取并渲染草稿内容。草稿模式在页面和应用中都可以工作。我们对现有的预览模式 API 进行了改进和简化,预览模式在页面中仍然有效。但是预览模式在应用中不起作用——你应该使用草稿模式。