Next.js 13 服务器组件和应用目录完整指南

通过关于使用服务器组件和应用程序目录的最完整和最权威的教程,释放 Next.js 13 的全部潜力。

目录

  • Next.js 13 带来了什么?
  • 服务器组件 (RSC)
  • 布局
  • Server Actions 服务器操作
  • Enhanced Router 增强型路由器
  • 什么是服务器组件?
  • 服务器组件与客户端组件
  • 定义服务器组件
  • 定义客户端组件
  • App 应用目录
  • 文件结构
  • 托管
  • Layouts 布局
  • 在布局组件中加载数据
  • 在服务器组件中使用Async/Await
  • 读取 Cookies 和 Headers
  • 从布局重定向
  • Next.js 重定向副作用
  • Pages 页面
  • 页面元数据和 SEO 搜索引擎优化
  • 加载指示器
  • 错误处理
    • 自定义 404 和 500 页
  • 字体
  • API 接口路由
  • 处理 Webhooks
  • Server Actions 服务器操作
  • 结论

Next.js 13 带来了什么?

Next.js 13 是最流行的 React 框架的主要版本:事实上,它附带了一个新的路由系统,也称为 App Router。在许多方面,这个新的路由系统是对前一个路由系统的完全重写,从根本上改变了我们将编写Next.js应用程序的方式

app 目录为现有 pages 目录带来了许多改进,这是在经过一段时间的实验性发布后编写 Next.js 应用的新默认方式。

服务器组件 (RSC)

新 App Router 中最大的变化是引入了服务器组件,这是一种在服务器上运行并返回发送到客户端的已编译 JSX 的新型 React 组件。服务器组件可用于呈现页面的骨架、从服务器并行获取数据并将其传递给“客户端组件”。

服务器组件是新 app 目录中的默认组件类型。

布局

布局由服务器组件提供支持,是环绕页面的基础组件。下一步.js布局不仅可用于跨页面显示通用 UI,还可用于跨页面重用逻辑和数据提取。

布局还解决了瀑布问题,这是当前 Next.js 路由系统的常见问题。事实上,使用新的 App Router,我们可以并行获取数据,并将其传递给页面组件:这是对当前路由系统的实质性性能改进

Server Actions 服务器操作

仍处于 alpha 阶段,服务器操作是一种在服务器上执行函数的新方法,而不是通过 API 处理程序连接它们。服务器操作对于执行服务器端逻辑(如发送电子邮件或更新数据库)非常有用。服务器操作对于提交表单、执行服务器端逻辑以及将用户重定向到新页面非常有用。

此外,服务器操作使我们能够重新验证从服务器组件获取的数据,从而消除了由于数据突变(例如更新 Redux 存储)而导致的复杂客户端状态管理的需求。

Enhanced Router 增强型路由器

使用常规文件名,我们可以在 app 目录中添加各种类型的组件。这是对当前路由系统的一大改进,当前路由系统要求我们使用特定的目录结构来定义页面、API 处理程序等。

这是什么意思?从现在开始,我们可以使用特定于 Next.js 的特定文件名约定在 app 目录中创建各种类型的组件:

  • 页面定义为 page.tsx
  • 布局定义为 layout.tsx
  • 错误定义为 error.tsx
  • 加载状态定义为 loading.tsx

什么是服务器组件?

服务器组件是一种新型的 React 组件,它们在服务器上运行并返回发送到客户端的已编译 JSX。Next.js 在 Next.js 13 中发布了新的应用目录,通过将服务器组件设置为默认类型组件,完全接受了服务器组件。

这是与在服务器和客户端上运行的传统 React 组件的重大转变。事实上,正如我们所指定的,React Server 组件不会在客户端上执行。

因此,我们需要记住使用服务器组件的一些约束:

  • 服务器组件不能使用仅限浏览器的 API
  • 服务器组件不能使用 React 钩子
  • 服务器组件无法使用 Context 上下文

那么,它们是干什么用的呢?

React Server 组件对于渲染页面的骨架很有用,同时将交互式位留给所谓的“客户端组件”。

尽管名称如此,但“客户端组件”(恕我直言,不幸的是)也是服务器呈现的,它们在服务器和客户端上运行。

React 服务器组件可能很有用,因为它们允许我们:

  • 更快地呈现页面
  • 减少需要发送到客户端的 JavaScript 数量
  • 提高服务器呈现页面的路由性能

简而言之,我们使用服务器组件从服务器获取数据并呈现页面的骨架:然后,我们可以将数据传递给“客户端组件”。

服务器组件与客户端组件

正如我们所看到的,服务器组件对于呈现页面的框架很有用,而客户端组件是我们今天所知道的组件。

Next.js文档中的这种比较是了解两者之间区别的好方法。

定义服务器组件

服务器组件不需要这样定义表示法:服务器组件在应用程序目录中呈现时是默认组件。

我们不能在服务器组件中使用 React 钩子、上下文或仅限浏览器的 API。但是,我们只能使用服务器组件 API,例如 headerscookies等。

服务器组件可以导入客户端组件

无需指定表示法来定义服务器组件:实际上,服务器组件是新 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,例如 headerscookies 等。

注意:客户端组件无法导入服务器组件,但您可以将服务器组件作为客户端组件的子组件或属性传递。

App 应用目录

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 将位于 /

Layouts 布局

布局是新应用路由器实现的最大新功能之一。

布局是包装页面的基础组件:这不仅可用于跨页面显示通用 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());
}

在上面的示例中:

  1. 我们使用 use 钩子在布局组件中获取数据
  2. 我们基于 data.user 属性有条件地呈现 ProfileDropdown 组件

注意:我们使用 use 钩子以(看似)同步的方式获取数据。这是因为 use 钩子在引擎盖下使用 Suspense ,这允许我们以同步方式编写异步代码。

在服务器组件中使用Async/Await

另一种方法是使组件成为 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());
}

读取 Cookies 和 Headers

如果您使用的是服务器组件,则可以从 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 重定向副作用

新的 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>
  );
}

Pages 页面

要在新的应用程序目录中定义页面,我们使用 特殊约定 page.tsx .这意味着,如果我们想在 app 目录中定义一个页面,我们需要 命名文件 page.tsx .

例如,如果我们想定义您网站的主页,我们可以将页面放在 app/(site) 目录中并命名为 page.tsx

function SitePage() {
  return <div>Site Page</div>;
}
 
export default SitePage;

页面元数据和 SEO 搜索引擎优化

要指定页面的元数据,我们可以导出 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 页面的原因。

自定义 404 和 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 路由。定义 API 路由的约定是在 app 目录中创建名为 route.tsx 的文件。

API 路由现在使用标准 Request 对象,而不是 express -like reqres 对象。

当我们定义 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');
}

处理 Webhooks

处理 webhook 是 API 路由的常见用例,现在获取原始正文请求要简单得多。实际上,我们可以使用以下 request.text() 方法获取原始正文请求:

// path: app/api/webhooks.tsx
export async function POST(
  request: Request
) {
  const rawBody = await request.text();
 
  // handle webhook here
}

Server Actions 服务器操作

服务器操作是 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 应用,并为未来提供面向未来的应用,而无需从旧架构进行痛苦的迁移。

你可能感兴趣的:(javascript,服务器,开发语言)