通过关于使用服务器组件和应用程序目录的最完整和最权威的教程,释放 Next.js 13 的全部潜力。
Next.js 13 是最流行的 React 框架的主要版本:事实上,它附带了一个新的路由系统,也称为 App Router。在许多方面,这个新的路由系统是对前一个路由系统的完全重写,从根本上改变了我们将编写Next.js应用程序的方式。
新 app
目录为现有 pages
目录带来了许多改进,这是在经过一段时间的实验性发布后编写 Next.js 应用的新默认方式。
新 App Router 中最大的变化是引入了服务器组件,这是一种在服务器上运行并返回发送到客户端的已编译 JSX 的新型 React 组件。服务器组件可用于呈现页面的骨架、从服务器并行获取数据并将其传递给“客户端组件”。
服务器组件是新 app
目录中的默认组件类型。
布局由服务器组件提供支持,是环绕页面的基础组件。下一步.js布局不仅可用于跨页面显示通用 UI,还可用于跨页面重用逻辑和数据提取。
布局还解决了瀑布问题,这是当前 Next.js 路由系统的常见问题。事实上,使用新的 App Router,我们可以并行获取数据,并将其传递给页面组件:这是对当前路由系统的实质性性能改进。
仍处于 alpha 阶段,服务器操作是一种在服务器上执行函数的新方法,而不是通过 API 处理程序连接它们。服务器操作对于执行服务器端逻辑(如发送电子邮件或更新数据库)非常有用。服务器操作对于提交表单、执行服务器端逻辑以及将用户重定向到新页面非常有用。
此外,服务器操作使我们能够重新验证从服务器组件获取的数据,从而消除了由于数据突变(例如更新 Redux 存储)而导致的复杂客户端状态管理的需求。
使用常规文件名,我们可以在 app
目录中添加各种类型的组件。这是对当前路由系统的一大改进,当前路由系统要求我们使用特定的目录结构来定义页面、API 处理程序等。
这是什么意思?从现在开始,我们可以使用特定于 Next.js 的特定文件名约定在 app
目录中创建各种类型的组件:
page.tsx
layout.tsx
error.tsx
loading.tsx
服务器组件是一种新型的 React 组件,它们在服务器上运行并返回发送到客户端的已编译 JSX。Next.js 在 Next.js 13 中发布了新的应用目录,通过将服务器组件设置为默认类型组件,完全接受了服务器组件。
这是与在服务器和客户端上运行的传统 React 组件的重大转变。事实上,正如我们所指定的,React Server 组件不会在客户端上执行。
因此,我们需要记住使用服务器组件的一些约束:
那么,它们是干什么用的呢?
React Server 组件对于渲染页面的骨架很有用,同时将交互式位留给所谓的“客户端组件”。
尽管名称如此,但“客户端组件”(恕我直言,不幸的是)也是服务器呈现的,它们在服务器和客户端上运行。
React 服务器组件可能很有用,因为它们允许我们:
简而言之,我们使用服务器组件从服务器获取数据并呈现页面的骨架:然后,我们可以将数据传递给“客户端组件”。
正如我们所看到的,服务器组件对于呈现页面的框架很有用,而客户端组件是我们今天所知道的组件。
Next.js文档中的这种比较是了解两者之间区别的好方法。
服务器组件不需要这样定义表示法:服务器组件在应用程序目录中呈现时是默认组件。
我们不能在服务器组件中使用 React 钩子、上下文或仅限浏览器的 API。但是,我们只能使用服务器组件 API,例如 headers
、 cookies
等。
服务器组件可以导入客户端组件。
无需指定表示法来定义服务器组件:实际上,服务器组件是新 app
目录中的默认组件类型。
假设该组件不是客户端组件的子组件 ServerComponent
,它将在服务器上呈现并作为编译的 JSX 发送到客户端:
export default function ServerComponent() {
return <div>Server Component</div>;
}
相反,在 Next.js app 目录中,我们需要专门定义客户端组件。
我们可以通过在文件顶部指定 use client
指令来做到这一点:
'use client';
export default function ClientComponent() {
return <div>Client Component</div>;
}
当我们使用客户端组件时,我们可以使用 React 钩子、上下文和仅限浏览器的 API。但是,我们不能仅使用某些服务器组件 API,例如 headers
、 cookies
等。
注意:客户端组件无法导入服务器组件,但您可以将服务器组件作为客户端组件的子组件或属性传递。
Next.js 13 中发布的新“app”目录是一种构建 Next.js 应用程序的新实验性新方法。它与目录共存,我们可以使用它来增量地将现有项目迁移到新的 pages
目录结构。
这种新的目录结构不仅仅是编写应用程序的新方法,它是一个全新的路由系统,比当前的路由系统强大得多。
新的 Next.js 13 文件结构是什么样的?让我们看一下我们将在本教程中使用的示例应用。
下面是具有新 app
目录的 Next.js 13 应用的示例:
- app
- layout.tsx
- (site)
- page.tsx
- layout.tsx
- app
- dashboard
- page.tsx
- layout.tsx
如您所见,文件的名称反映了组件的类型。例如,是布局组件,而 page.tsx
是页面组件, layout.tsx
依此类推。
别担心,我们将在下一节中介绍所有不同类型的组件。
新 app
目录的一个重要副作用是它允许我们共置文件。由于文件名是约定俗成的,我们可以定义 app
目录中的任何文件,而不会成为页面组件。
例如,我们可以将特定页面的组件直接放在定义它的文件夹中:
- app
- (site)
- components
- Dashboard.tsx
- hooks
- use-fetch-data-hook.ts
- page.tsx
为什么在 (site)
括号里?通过使用括号,我们使目录 site
“无路径”,这意味着我们可以在 site 目录中创建新的布局、加载文件和页面,而无需向路由添加新的路径段。
(site)
下的所有页面都将从根路径 /
访问:例如,该页面 app/(site)/page.tsx
将位于 /
。
布局是新应用路由器实现的最大新功能之一。
布局是包装页面的基础组件:这不仅可用于跨页面显示通用 UI,还可用于重用数据获取和逻辑。
Next.js 需要一个根布局组件:
export const metadata = {
title: 'Next.js Tutorial',
description: 'A Next.js tutorial using the App Router',
};
async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang={'en'}>
<body>{children}</body>
</html>
);
}
export default RootLayout;
布局是使用 app
目录中的约定 layout.tsx
定义的:Next.js将自动包装定义布局的文件夹中的所有页面。
例如,如果我们在 中 app/(site)/layout.tsx
定义了一个布局,Next.js 将使用此布局包装 app/(site)
目录中的所有页面:
export default async function SiteLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div>
<main>
{children}
</main>
</div>
);
}
结果 - app/(site)
目录中的所有页面都将用组件 SiteLayout
包装。
如果您需要加载目录的所有页面所需的一些数据,布局组件也非常有用:例如,我们可以在布局组件中加载用户的配置文件,并将其传递给页面组件。
要在 Next.js 的布局组件中获取数据,我们可以使用新 use
钩子,这是 React 中的一个实验性钩子,用于 Suspense
获取服务器上的数据。
import { use } from "react";
export default function SiteLayout({
children,
}: {
children: React.ReactNode;
}) {
const data = use(getData());
return (
<div>
<header>
{ data.user ? <ProfileDropown /> : null }
</header>
<main>
{children}
</main>
</div>
);
}
function getData() {
return fetch('/api/data').then(res => res.json());
}
在上面的示例中:
use
钩子在布局组件中获取数据data.user
属性有条件地呈现 ProfileDropdown
组件注意:我们使用
use
钩子以(看似)同步的方式获取数据。这是因为use
钩子在引擎盖下使用Suspense
,这允许我们以同步方式编写异步代码。
另一种方法是使组件成为 async
组件,并使用 async/await
从以下位置 getData
获取数据:
export default async function SiteLayout({
children,
}: {
children: React.ReactNode;
}) {
const data = await getData()
return (
<div>
<header>
{ data.user ? <ProfileDropown /> : null }
</header>
<main>
{children}
</main>
</div>
);
}
function getData() {
return fetch('/api/data').then(res => res.json());
}
如果您使用的是服务器组件,则可以从 next/headers
包中使用读取 Cookies 和 Headers。
注意:在撰写本文时,我们只能使用这些函数来读取它们的值,而不能设置或删除它们。
import { cookies } from 'next/headers';
export function Layout(
{ children }: { children: React.ReactNode },
) {
const lang = cookies.get('lang');
return (
<html lang={lang}>
<body>
{children}
</body>
</html>
);
}
如果您觉得缺少某些东西,请不要担心,不仅仅是您。实际上,与 不同 getServerSideProps
,我们无法访问该 request
对象。这就是 Next.js 公开这些实用程序以从请求中读取数据的原因。
在布局中,我们还可以将用户重定向到不同的页面。
例如,如果我们想将用户重定向到登录页面(如果他们未经身份验证),我们可以在布局组件中执行此操作:
import { use } from 'react';
import { redirect } from 'next/navigation';
function AuthLayout(
props: React.PropsWithChildren,
) {
const session = use(getSession());
if (session) {
return redirect('/dashboard');
}
return (
<div className={'auth'}>
{props.children}
</div>
);
}
function getSession() {
return fetch('/api/session').then(res => res.json());
}
现在,我们可以在布局组件中使用 loadSession
函数:
import { use } from 'react';
function AuthLayout(
props: React.PropsWithChildren,
) {
const response = use(loadSession());
const data = response.data;
// do something with data
return (
<div className={'auth'}>
{props.children}
</div>
);
}
新的 Next.js 函数 redirect
将抛出错误:实际上,它的返回类型是 never
。如果发现错误,则需要小心并确保遵循错误引发的重定向。
为此,我们可以使用 Next.js 包导出的一些实用程序:
import { use } from 'react';
import {
isRedirectError,
getURLFromRedirectError,
} from 'next/dist/client/components/redirect';
import { redirect } from "next/navigation";
async function loadData() {
try {
const data = await getData();
if (!data) {
return redirect('/login');
}
const user = data.user;
console.log(`User ${user.name} logged in`);
return user;
} catch (e) {
if (isRedirectError(e)) {
return redirect(getURLFromRedirectError(e));
}
throw e;
}
}
function Layout(
props: React.PropsWithChildren,
) {
const data = use(loadData());
// do something with data
return (
<div>
{props.children}
</div>
);
}
要在新的应用程序目录中定义页面,我们使用 特殊约定 page.tsx
.这意味着,如果我们想在 app
目录中定义一个页面,我们需要 命名文件 page.tsx
.
例如,如果我们想定义您网站的主页,我们可以将页面放在 app/(site)
目录中并命名为 page.tsx
:
function SitePage() {
return <div>Site Page</div>;
}
export default SitePage;
要指定页面的元数据,我们可以导出 page.tsx
文件中的 constant metadata
属性:
export const metadata = {
title: 'Site Page',
description: 'This is the site page',
};
如果需要访问动态数据,可以使用以下 generateMetadata
函数:
// path: app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({
slug: post.slug,
}));
}
查看生成静态路径的完整文档。
在页面之间导航时,我们可能希望显示加载指示器。为此,我们可以使用可以在每个目录中定义的 loading.tsx
文件:
// path: /app/loading.tsx
export default function Loading() {
return <div>Loading...</div>;
}
在这里,您可以添加要在页面加载时显示的任何组件,例如顶部栏加载器或加载微调器,或两者兼而有之。
目前,您可以使用约定 not-found.tsx
定义“404 未找到”页面:
export default function NotFound() {
return (
<>
<h2>Not Found</h2>
<p>Could not find requested resource</p>
</>
);
}
仅当与 notFound
函数结合使用时,才会显示此文件。这就是为什么仍然建议使用旧 pages
目录使用自定义 400 和 500 页面的原因。
在撰写本文时,我们需要坚持使用常规 pages
目录来定义自定义 404 和 500 页面。这是因为 Next.js 不支持 app
目录中的自定义 404 和 500 页面。
我们可以使用该包 next/font
在我们的应用程序中加载字体。
为此,我们需要定义一个客户端组件,并将其导入根布局 app/layout.tsx
文件中:
// path: app/font.tsx
'use client';
import { Inter } from 'next/font/google';
import { useServerInsertedHTML } from 'next/navigation';
const heading = Inter({
subsets: ['latin'],
variable: '--font-family-heading',
fallback: ['--font-family-sans'],
weight: ['400', '500'],
display: 'swap',
});
export default function Fonts() {
useServerInsertedHTML(() => {
return (
<style
dangerouslySetInnerHTML={{
__html: `
:root {
--font-family-sans: '-apple-system', 'BlinkMacSystemFont',
${sans.style.fontFamily}, 'system-ui', 'Segoe UI', 'Roboto',
'Ubuntu', 'sans-serif';
--font-family-heading: ${heading.style.fontFamily};
}
`,
}}
/>
);
});
return null;
}
之后,我们可以在根布局中导入 Fonts
组件:
import Fonts from '~/components/Fonts';
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<Fonts />
<body>{children}</body>
</html>
);
}
新的应用目录还支持 API 路由。定义 API 路由的约定是在 app
目录中创建名为 route.tsx
的文件。
API 路由现在使用标准 Request 对象,而不是 express -like req
和 res
对象。
当我们定义 API 路由时,我们可以导出我们想要支持的方法的处理程序。例如,如果我们想支持 GET 和 POST 方法,我们可以导出 GET 和 POST 函数:
// path: app/api/route.tsx
import { NextResponse } from 'next/server';
export async function GET() {
return NextResponse.json({ hello: 'world' });
}
export async function POST(
request: Request
) {
const body = await request.json();
const data = await getData(body);
return NextResponse.json(data);
}
如果我们想操纵响应,例如通过设置 cookie,我们可以使用以下 NextResponse 对象:
export async function POST(
request: Request
) {
const organizationId = getOrganizationId();
const response = NextResponse.json({ organizationId });
response.cookies.set('organizationId', organizationId, {
path: '/',
httpOnly: true,
sameSite: 'lax',
});
return response;
}
在 API 路由中,就像在服务器组件中一样,我们也可以使用从以下位置 next/navigation
导入的 redirect
函数重定向用户:
import { redirect } from 'next/navigation';
export async function GET(
request: Request
) {
return redirect('/login');
}
处理 webhook 是 API 路由的常见用例,现在获取原始正文请求要简单得多。实际上,我们可以使用以下 request.text()
方法获取原始正文请求:
// path: app/api/webhooks.tsx
export async function POST(
request: Request
) {
const rawBody = await request.text();
// handle webhook here
}
服务器操作是 Next.js 13 中引入的新概念。它们是定义可从客户端调用的服务器端操作的一种方法。您需要做的就是定义一个函数并使用顶部的 use server
关键字。
例如,下面是一个有效的服务器操作:
async function myActionFunction() {
'use server';
// do something
}
如果要从客户端组件定义服务器操作,则需要从单独的文件导出该操作,并将其导入到客户端组件中。该文件需要顶部的关键字 use server
。
'use server';
async function myActionFunction() {
// do something
}
要从客户端调用服务器操作,您可以通过多种方式
action
定义为 form
组件的属性formAction
属性从 button
组件调用操作useTransition
钩子调用操作(如果它改变了数据)如果您想了解有关服务器操作的更多信息,请查看关于 Next.js 服务器操作的文章。
在本文中,我们学习了如何使用 Next.js 13 中的新实验性应用路由器。
我们在本文中学到的模式和约定仍处于实验阶段,将来可能会发生变化。但是,它们已经非常有用,我们已经可以在项目中开始使用它们。
您是否知道您可以我们的 Next.js 13 SaaS 初学者工具包来构建您自己的 SaaS 应用程序?这是一个功能齐全的 SaaS 初学者工具包,其中包含使用 Next.js 13 构建 SaaS 应用程序所需的一切,包括身份验证、计费等。
虽然它仍处于实验阶段,但你可以立即开始使用 Next.js 13 开始构建 SaaS 应用,并为未来提供面向未来的应用,而无需从旧架构进行痛苦的迁移。