头图

如何在Next.js使用Blitz.js的功能

前言

大家好,我是倔强青铜三。是一名热情的软件工程师,我热衷于分享和传播IT技术,致力于通过我的知识和技能推动技术交流与创新,欢迎关注我,微信公众号:倔强青铜三。欢迎点赞收藏关注一键三连!!!

@blitzjs/next

@blitzjs/next 适配器公开了特定于 Next.js 框架的函数和组件。

概览

@blitzjs/next 适配器公开了特定于 Next.js 框架的函数和组件。

安装

你可以通过运行以下命令来安装 @blitzjs/next

npm i @blitzjs/next # yarn add @blitzjs/next # pnpm add @blitzjs/next

Next 配置

Blitz.js 通过接受一个 blitz 属性来扩展 next.config.js 文件。

blitz?: {
  resolverPath?: ResolverPathOptions;
  customServer?: {
      hotReload?: boolean;
  };
};

注意事项

对于设置自定义 resolverPath 的更多信息,请查阅 RPC 规范

客户端

示例

src/blitz-client.ts 中:

import { setupBlitzClient } from "@blitzjs/next"

export const { withBlitz } = setupBlitzClient({
  plugins: [],
})

然后在 src/pages/_app.tsx 中用 withBlitz 高阶组件包裹 MyApp 函数。

import {
  ErrorFallbackProps,
  ErrorComponent,
  ErrorBoundary,
} from "@blitzjs/next"
import { AuthenticationError, AuthorizationError } from "blitz"
import type { AppProps } from "next/app"
import React, { Suspense } from "react"
import { withBlitz } from "src/blitz-client"

function RootErrorFallback({ error }: ErrorFallbackProps) {
  if (error instanceof AuthenticationError) {
    return <div>Error: 您尚未认证</div>
  } else if (error instanceof AuthorizationError) {
    return (
      <ErrorComponent
        statusCode={error.statusCode}
        title="抱歉,您无权访问此页面"
      />
    )
  } else {
    return (
      <ErrorComponent
        statusCode={(error as any)?.statusCode || 400}
        title={error.message || error.name}
      />
    )
  }
}

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ErrorBoundary FallbackComponent={RootErrorFallback}>
      <Component {...pageProps} />
    </ErrorBoundary>
  )
}

export default withBlitz(MyApp)

注意事项

<ErrorBoundary /> 提供商和 <ErrorComponent /> 组件由 @blitzjs/next 提供。

API

setupBlitzClient({
  plugins: [],
})

参数

  • plugins: Blitz.js 插件数组

    • 必需

返回值

返回一个带有 withBlitz HOC 包装器的对象

服务器端

示例

src/blitz-server.ts

import { setupBlitzServer } from "@blitzjs/next"

export const { gSSP, gSP, api } = setupBlitzServer({
  plugins: [],
})

API

setupBlitzServer({
  plugins: [],
  onError?: (err) => void
})

参数

  • plugins: Blitz.js 插件数组

    • 必需
  • onError: 捕获所有错误 (适用于 Sentry 等服务)

返回值

返回一个带有 gSSP, gSPapi 包装器的对象。

自定义 Next.js 服务器

Blitz CLI 支持运行自定义 Next.js 服务器。这意味着你可以在 Blitz.js CLI 注入环境变量的同时编译 JavaScript 和 TypeScript。默认情况下,CLI 会检查 src/server/index.[ts | js]src/server.[ts | js]

有关自定义 Next.js 服务器的更多信息,请查看 官方文档

包装器

所有 Next.js 包装函数都使用 superjson 序列化。这意味着你可以在返回数据时使用 Date, Map, SetBigInt。另外需要注意的是,Blitz 在调用 Next.js 请求处理程序之前运行插件中的中间件。

注意事项

如果你使用认证插件,gSSP, gSPapi 函数都会传递会话的上下文。有关认证插件的更多信息,请查阅 blitzjs/auth

包装器示例

getStaticProps

import { gSP } from "src/blitz-server"

export const getStaticProps = gSP(async ({ ctx }) => {
  return {
    props: {
      data: {
        userId: ctx?.session.userId,
        session: {
          id: ctx?.session.userId,
          publicData: ctx?.session.$publicData,
        },
      },
    },
  }
})

getServerSideProps

import { gSSP } from "src/blitz-server"

export const getServerSideProps = gSSP(async ({ ctx }) => {
  return {
    props: {
      userId: ctx?.session.userId,
      publicData: ctx?.session.$publicData,
    },
  }
})

api

import { api } from "src/blitz-server"

export default api(async (req, res, ctx) => {
  res.status(200).json({ userId: ctx?.session.userId })
})

有关 Next.js API 路由的更多信息,请访问他们的文档:https://nextjs.org/docs/api-routes/introduction

概念

页面加载前验证用户

你可能希望在页面加载前检查用户是否已登录。我们将在 getServerSideProps() 中直接调用查询。然后你可以在服务器端检查用户是否已登录,并使用内置的 Next.js 重定向属性。

import { Routes, BlitzPage } from "@blitzjs/next"
import { gSSP } from "src/blitz-server"
import getCurrentUser from "src/users/queries/getCurrentUser"

export const getServerSideProps = gSSP(async ({ ctx }) => {
  const currentUser = await getCurrentUser(null, ctx)

  if (currentUser) {
    return {
      props: {
        user: currentUser,
      },
    }
  } else {
    return {
      redirect: {
        destination: Routes.LoginPage(),
        permanent: false,
      },
    }
  }
})

服务器端数据获取时的返回类型

你可以设置 Next.js 数据获取函数返回的类型。所有 Blitz.js 对 Next.js 函数的包装器都接受一个泛型。BlitzPage 类型也是如此。

例如,我们可以使用一些 TypeScript 工具来帮助获取 getCurrentUser() 返回的类型。

import { Routes, BlitzPage } from "@blitzjs/next"
import { gSSP } from "src/blitz-server"
import getCurrentUser from "src/users/queries/getCurrentUser"

type TCurrentUser = Awaited<ReturnType<typeof getCurrentUser>>

export const getServerSideProps = gSSP<{ user: TCurrentUser }>(
  async ({ ctx }) => {
    const currentUser = await getCurrentUser(null, ctx)

    if (currentUser) {
      return {
        props: {
          user: currentUser,
        },
      }
    } else {
      return {
        redirect: {
          destination: Routes.LoginPage(),
          permanent: false,
        },
      }
    }
  }
)

const Page: BlitzPage<{ user: TCurrentUser }> = ({ user }) => {
  return (
    <Layout title="Page">
      <div className="container">
        <p>User Page</p>
        {user && <p>{user.email}</p>}
      </div>
    </Layout>
  )
}

export default Page

页面初始加载时处理错误

有一个边缘情况是,你可能在初始页面加载时调用的查询中抛出错误,导致服务器错误而不是触发 <ErrorBoundary />。这是因为在 _app.tsx 挂载之前,初始加载页面时没有渲染 ErrorBoundary 组件。虽然这是预期的行为,但有一个解决办法。

例如,在一个查询中,如果用户未找到,你可以创建一个 NotFoundError() 然后返回状态码。

export default resolver.pipe(
  resolver.zod(GetUser),
  async (input) => {
    const { id } = input

    const user = await db.user.findFirst({ where: { id } })

    if (!user) {
      const userError = new NotFoundError("User not found")
      return {
        error: userError.statusCode,
      }
    } else {
      return {
        user,
      }
    }
  }
)

然后在服务器端(例如 getServerSideProps())你可以调用查询,如果返回对象中找到错误键,则显示错误。

export const getServerSideProps = gSSP(async ({ ctx }) => {
  const user = await getUser({ 1 }, ctx)
  if ("error" in user) {
    return { props: { error: user.error } }
  } else {
    return { props: { user } }
  }
})

你也可以在 _app.tsx 中捕获服务器错误,并使用 toast 组件显示错误。

function MyApp({ Component, pageProps }: AppProps) {
  const getLayout = Component.getLayout || ((page) => page)
  if (pageProps.error) {
    return <ToastComponent>{pageProps.error.statusCode}</ToastComponent>
  }
  return (
    <ErrorBoundary FallbackComponent={RootErrorFallback}>
      {getLayout(<Component {...pageProps} />)}
    </ErrorBoundary>
  )
}
最后感谢阅读!欢迎关注我,微信公众号倔强青铜三。欢迎点赞收藏关注,一键三连!!!

倔强青铜三
23 声望0 粉丝