🚨

【Next.js】tRPCを用いたRSCでのエラーハンドリング

2024/09/10に公開

はじめに

エラーハンドリングに力を入れていますか?
エラーハンドリングは、アプリケーションの安定性と信頼性を保つ上で欠かせない要素です。適切なハンドリングができていないと、ユーザー体験が損なわれるだけでなく、デバッグやメンテナンスの効率も大きく低下します。
今回は、エラーの種類ごとに最適なハンドリング方法を考察し、App RouterやtRPCを使用した具体例を用いて、いくつかのエラーハンドリングの手法を紹介していきます。
技術スタックが異なる場合でも読める内容になっていますので、エラーハンドリングを実装するための参考になれば嬉しいです。

エラーハンドリングの重要性

エラーハンドリングは、アプリケーション開発において以下のようなとても重要な役割を果たします。

  • UI/UXの向上
    エラーが適切に処理されないと、ユーザーは予期せぬクラッシュやエラーメッセージに直面します。これにより、ユーザーが混乱し、アプリケーションに対する信頼を失う可能性があります。適切なエラーハンドリングにより、ユーザーに対して分かりやすいフィードバックを提供し、必要に応じて次のステップを案内することができます。
  • デバッグとメンテナンスの容易化
    適切なエラーハンドリングにより、開発者は問題を迅速に特定・修正できます。具体的なエラーメッセージやログを活用することで、デバッグやメンテナンスがスムーズに進みます。

エラーの種類とハンドリング方法

エラーの種類を発生場所に応じてクライアントサイドのエラーサーバーサイドのエラーAPI通信中のエラーに分類し、それぞれどのようにハンドリングすべきかを考えてみました。
さらに、これらのエラーを6つのエラーハンドリング方法に当てはめ、後項ではそれぞれの実装例を紹介します。

  • ①フォームバリデーション
  • error.tsxglobal-error.tsx
  • not-found.tsx
  • ④Next.jsのmiddleware
  • ⑤tRPCのプロシージャ
  • ⑥tRPCのmiddleware

クライアントサイドのエラー

  • バリデーションエラー:入力フォームで不正な値が入力された際のエラー
    ①フォームバリデーション
    クライアントサイドやサーバーサイドでフォームの入力内容を検証し、不正なデータが送信されないようにします。これにより、無駄なリクエストを防ぐとともに、ユーザーにすぐにフィードバックを返します。(APIリクエストをする前のハンドリングという点で今回はクライアントサイドのエラーに分類しました。)

  • UIエラー:Reactレンダリング中のエラー
    ②error.tsx、global-error.tsx
    UIエラーのような予期せぬエラーは、ユーザーにエラーメッセージを表示したり、再試行の誘導をしたりしてアプリケーション全体がクラッシュしないように気をつける必要があります。

  • 404エラー:指定されたリソースやページが見つからない場合のエラー
    ③not-found.tsx
    404エラーは、クライアントがサーバーにリクエストを送信した際、指定されたリソースやページが存在しない場合に発生します。この場合、not-found.tsxを使ってユーザーに404エラーページを表示し、ユーザーが迷わないように適切なナビゲーションを提供します。ユーザーが別のページやホームにアクセスできるリンクを設け、スムーズに正しい情報にたどり着けるよう誘導することが重要です。

サーバーサイドのエラー

  • 認証・認可エラー(リソースアクセス):未認証や権限のないユーザーがページやリソースにアクセスしようとした場合のエラー
    ④Next.jsのmiddleware
    認証・認可エラーは、未認証のユーザーが認証が必要なページにアクセスしようとした場合や、認証済みでもアクセス権を持たないリソースにアクセスしようとした場合に発生します。Next.jsのmiddlewareを使って、リクエストがサーバーに到達する前に認証・認可チェックを一貫して行います。

  • 認証・認可エラー(tRPCのエンドポイントアクセス):未認証や権限のないユーザーがtRPCのエンドポイントにアクセスした際のエラー
    ⑥tRPCのmiddleware
    tRPCのmiddlewareは、APIリクエストに対して認証と認可のチェックを行う役割を担います。未認証やアクセス権限のないユーザーからのリクエストをサーバー側で適切に処理し、エラーレスポンスを返すことで、リソースへの不正なアクセスを防ぎます。

  • データベースエラー:データベースへのクエリ実行や接続に失敗した際のエラー

  • サーバー内部エラー:サーバー側の処理中に発生する予期せぬエラー(内部ロジックの不具合など)
    ⑤tRPCのプロシージャ
    tRPCのエンドポイントでデータベースクエリの失敗や接続エラーやサーバー内部エラーが発生した場合、tRPCがそのエラーをキャッチしてクライアントに適切なエラーレスポンスを返します。
    クライアント側ではセキュリティ上、詳細なエラーメッセージをユーザーに知らせず、「内部エラーが発生しました。後ほど再試行してください」といった一般的なメッセージを表示します。サーバー内部では、詳細なログを記録することで、エラーの原因を特定し、迅速に対処できるようにします。

API通信中のエラー

  • ネットワークエラー:クライアントとサーバー間の通信が失敗した際に発生するエラー
  • タイムアウトエラー:サーバーからの応答が指定された時間内に返ってこなかった際に発生するエラー
    ⑤tRPCのプロシージャ
    ネットワークの接続が失敗した場合やサーバーからの応答が遅れ、タイムアウトが発生した場合、tRPCはその通信エラーをキャッチします。クライアント側では、ネットワークの不具合を検出し、ユーザーに「接続が失敗しました。再試行してください」といったメッセージを表示し、再試行を促します。

エラーハンドリングの実装例

ここからはApp RouterとtRPCを使用した具体例を用いて、いくつかのエラーハンドリングの実装例を紹介していきます。

①フォームバリデーション

ユーザーの入力が不正な場合、APIリクエストを送信する前に詳細なエラーメッセージを表示することで、UI/UXが向上します。クライアントサイドではReact Hook Formなどのライブラリを使用し、サーバーサイドでのエラーハンドリングにはServer Actionsを活用して実装できます。今回は、Server Actionsでのバリデーション方法を紹介します。

Server Actionsとは

Server ActionsはNext.jsのv13で登場し、v14で安定版となった機能です。<form>タグにaction属性としてServer Actionsを設定することでサーバーサイドで実行される関数をClient Componentsから直接呼び出すことができます。

Server Actionsでのフォーム検証

サーバーサイドではZodを使用してバリデーションを行い、エラーがあればそのエラーをクライアントに返します。クライアント側では、useFormStateを使用してこれらのエラーメッセージを表示します。

以下は、Zodを用いたサーバーサイドでのバリデーションの実装例です。
フォーム送信時にサーバーサイドで実行される関数の定義です。

src/lib/actions.ts
"use server";
import { z } from "zod";
import { api } from "@/trpc/server";

export type ZodErrors = {
  message?: string[] | undefined;
  name?: string[] | undefined;
} | null;

export type State = {
  errorMessage: ZodErrors;
};

// フォームのスキーマ設定
const schema = z.object({
  name: z
    .string()
    .min(3, { message: "ユーザー名は3文字以上で入力してください" }),
  message: z
    .string()
    .min(3, { message: "メッセージは3文字以上で入力していください" }),
});

export async function clientFormAction(prevState: State, formData: FormData) {
  // formData から取得したデータをバリデーション
  const validatedFields = schema.safeParse({
    name: formData.get("name"),
    message: formData.get("message"),
  });

  if (validatedFields.error) {
    const errorMessages = validatedFields.error.flatten().fieldErrors;
    return { errorMessage: errorMessages };
  }

  await api.chat.create({ name: validatedFields.data.message });
  return { errorMessage: null };
}

schema.safeParseを使用して、formDataから取得したデータのバリデーションを実施します。

クライアントサイドでは、ReactのuseFormStateフックを使用して、サーバーサイドから返されたエラーメッセージを表示します。

src/components/ClientForm.tsx
"use client";
import { clientFormAction, State, ZodErrors } from "@/lib/actions";
import { useFormState } from "react-dom";

const initialState: State = { errorMessage: null };

export function ClientForm() {
  const [state, formAction] = useFormState(clientFormAction, initialState);

  return (
    <div>
      <form action={formAction}>
        <input type="text" name="name" />
        {state?.errorMessage?.name && <p>{state.errorMessage.name}</p>}

        <input type="text" name="message" />
        {state?.errorMessage?.message && <p>{state.errorMessage.message}</p>}
        <button type="submit" defaultValue="">
          送信
        </button>
      </form>
    </div>
  );
}

useFormStateでエラーメッセージのstateを管理しています。
state.errorMessageは、サーバーサイドから返されたバリデーションエラーメッセージを格納しています。
ユーザーがフォームを送信すると、サーバーサイドでバリデーションが行われ、エラーがあればそのメッセージがクライアントに返され、フォームに表示されます。

error.tsxglobal-error.tsx

error.tsxは予期せぬエラーをキャッチするために使用されます。UIエラーやランタイムエラーなど、適切にハンドリングされなかったエラーを最終的にキャッチし、ユーザーにフォールバックUIを表示する役割を果たします。

https://nextjs.org/docs/app/building-your-application/routing/error-handling

error.tsxの設置方法

error.tsxを使用するには、ルートセグメント内にerror.tsxファイルを追加するだけです。ディレクトリツリーの例は以下の通りです。

app/
  ├── dashboard/
  │   ├── page.tsx
  │   ├── layout.tsx
  │   ├── templete.tsx
  │   └── error.tsx # dashboardセグメント内で発生するエラーをキャッチ
  ├── layout.tsx
  ├── templete.tsx
  └── page.tsx

このツリー構造では、dashboardセグメント内で発生するエラーをキャッチして、error.tsxでハンドリングします。
error.tsxは全てのディレクトリに配置する必要はなく、エラーが発生する可能性があるページにのみ配置すれば十分です。

error.tsxの実装方法

error.tsxは内部でReactの Error Boundaryを利用しているため、Client Componentsとして機能します。そのため、ファイルの先頭に"use client"ディレクティブを追加する必要があります。

app/dashboard/error.tsx
"use client"

import { useEffect } from 'react'
 
export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string }
  reset: () => void
}) {
  useEffect(() => {
    console.error(error)
  }, [error])
 
  return (
    <div>
      <h2>エラーが発生しました</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>再実行</button>
    </div>
  )
}

エラーの原因は一時的なものである場合があるため、もう一度試すと問題が解決する可能性があります。
そこで、reset()関数は、エラーが発生したコンポーネントを再レンダリングできます。そのため、ユーザーに再試行を促すことができます。

global-error.tsxの設置方法

error.tsxの注意点としてはlayout.tsxの内側の層に位置するため、同階層のlayout.tsxtemplate.jsのエラーをキャッチすることはできません。そのため、親セグメントのerror.tsxでキャッチする必要があります。
ルートレイアウトのエラーをハンドリングするためには別途global-error.tsxを使用する必要があります。ディレクトリツリーの例は以下の通りです。

app/
  ├── dashboard/
  │   ├── page.tsx
  │   ├── layout.tsx
  │   ├── templete.tsx
  │   └── error.tsx
  ├── layout.tsx
  ├── templete.tsx
  ├── global-error.tsx # ルートレイアウトのエラーをキャッチ
  └── page.tsx

error.tsxglobal-error.tsxの設置によるコンポーネントツリーのイメージです。

<RootLayout>
  <ErrorBoundary fallback={<GlobalError />}> // 特別なGlobalErrorがルートレイアウトのエラーをキャッチする
...
    <ErrorBoundary fallback={<Error />}>
      <Layout> // 親セグメントでエラーをキャッチする
        <Template> // 親セグメントでエラーをキャッチする
          <ErrorBoundary fallback={<Error />}> // Pageのエラーをキャッチ
            <Page />
          </ErrorBoundary>
        </Template>
      </Layout>
    </ErrorBoundary>
...
  </ErrorBoundary>
</RootLayout>

global-error.tsxは通常のError Boundaryとは異なる方法で実装されているためルートレイアウトのエラーをキャッチすることができます。

内部構造

vercelの出しているAIのv0に聞いてみたところ概念的には以下のようになっているらしい。

try {
  <RootLayout>
    <ErrorBoundary fallback={<GlobalError />}>
      {/* アプリケーションの残りの部分 */}
    </ErrorBoundary>
  </RootLayout>
} catch (error) {
  <GlobalError error={error} />
}

global-error.tsxの実装方法

この場合、ルートレイアウトがクラッシュし利用できない可能性があるため代わりにglobal-error.tsx内に必ず<html><body>タグを含める必要があります。

app/global-error.tsx
"use client";

export default function GlobalError({
  error,
  reset,
}: {
  error: Error;
  reset: () => void;
}) {
  return (
    <html>
      <body>
        <h2>ルートレイアウトでエラーが発生しました</h2>
        <p>{error.message}</p>
        <button onClick={() => reset()}>もう一度実行する</button>
      </body>
    </html>
  );
}

not-found.tsx

クライアントがサーバーにリクエストを送信した際、指定されたリソースやページが存在しない場合にはnot-found.tsxを使用して404エラーページを作成することができます。
not-found.tsxはNext.jsのApp Routerで使用されるファイルで、特定のページが見つからない場合に表示されるカスタム404エラーページを定義するためのものです。

https://nextjs.org/docs/app/api-reference/file-conventions/not-found

not-found.tsxの設置方法

アプリケーションの特定のルートディレクトリに配置します。

app/
  ├── not-found.tsx
  └── page.tsx

not-found.tsxの実装方法

デフォルトではServer Componentsになります。404エラー時に表示するUIを定義します。

app/not-found.tsx
export default function NotFound() {
  return (
    <div>
      <h1>404 - ページが見つかりません</h1>
      <p>お探しのページは存在しないか、移動した可能性があります。</p>
    </div>
  );
}

④Next.jsのmiddleware

middlewareは、クライアントから送信されたリクエストがサーバーのメイン処理に進む前に実行されます。主な用途として、認証エラーや認可エラーのチェック、リクエストの正当性確認などがあります。例えば、ユーザーが適切な認証情報を持っているか、特定のリソースにアクセスする権限があるか、リクエストヘッダーやパラメータが正しい形式であるかなどをチェックします。

middlewareの設置方法

プロジェクトのルートディレクトリにmiddleware.tsファイルを設置します。

  ├── app/         
  │    ├── page.tsx  
  │    ├── layout.tsx 
  │    └── dashboard/ 
  │        ├── page.tsx   
  │        └── layout.tsx 
  ├── middleware.ts  
  ├── public/      
  └── ... 

middlewareの実装

src/middleware.ts
import { cookies } from "next/headers";
import { NextRequest, NextResponse } from "next/server";

export function middleware(request: NextRequest) {
  const token = cookies().get("access_token")?.value;
  // トークンのチェックなど
  ...

  if (!token) {
    // 未ログインでログインが必要なページにアクセス -> ログインページにリダイレクト
    return NextResponse.redirect(new URL("/login", request.url));
  }
  return NextResponse.next();
}

// matcherでmiddlewareの適用範囲を指定
export const config = {
  matcher: ['/dashboard/:path*'], // 例えば、/dashboard配下のすべてのルートで有効
};

一例ですが、cookiesを使用して現在のリクエストに含まれるクッキー情報を取得し、ユーザーがログインしているかを確認します。トークンが存在しない、有効期限が切れているなどの場合(未ログイン)、ログインページにリダイレクトします。
matcherで指定されたパスに対して、ミドルウェアが適用されます。これにより、特定のルートに対してのみミドルウェアを適用することができます。

⑤tRPCのプロシージャ

サーバー内部やAPIにおけるバックエンドとフロントエンド間の通信で発生したエラーを適切にキャッチし、エラーをフロントエンドに伝達する役割があります。

エラーフォーマット

tRPCのプロシージャでエラーが発生すると以下のようなエラープロパティを含むオブジェクトをクライアントに伝達します。この情報をもとにクライアント側はエラーを適切に処理します。
不正なリクエスト入力によってクライアントエラーによりリクエストを処理できない場合に発生したエラー応答の例を次に示します。

{
  "id": null,
  "error": {
    "message": "\"password\" must be at least 4 characters",
    "code": -32600,
    "data": {
      "code": "BAD_REQUEST",
      "httpStatus": 400,
      "stack": "...",
      "path": "user.changepassword"
    }
  }
}
tRPCの定義するエラーコードとHTTPコード

以下は、tRPCが定義するエラーコードと、それぞれが示すエラーの種類および対応するHTTPコードについての説明です。

コード 説明 HTTPコード
BAD_REQUEST クライアントエラーによりリクエストを処理できない場合 400
UNAUTHORIZED 認証資格が不足している場合 401
FORBIDDEN 必要なデータソースにアクセスできない場合 403
NOT_FOUND リソースが見つからない場合 404
METHOD_NOT_SUPPORTED リクエストメソッドがサポートされていない場合 405
TIMEOUT 未使用の接続がタイムアウトした場合 408
CONFLICT リソースの状態が競合している場合 409
PRECONDITION_FAILED リソースへのアクセスが拒否された場合 412
PAYLOAD_TOO_LARGE リクエストがサーバーの制限を超えている場合 413
UNSUPPORTED_MEDIA_TYPE ペイロード形式がサポートされていない場合 415
UNPROCESSABLE_CONTENT リクエストが理解されるが処理できない場合 422
TOO_MANY_REQUESTS リクエストが過剰に送信された場合 429
CLIENT_CLOSED_REQUEST クライアントがリクエストを中断した場合 499
INTERNAL_SERVER_ERROR サーバー内部でエラーが発生した場合 500
NOT_IMPLEMENTED サーバーが機能をサポートしていない場合 501
BAD_GATEWAY 上位サーバーから無効なレスポンスを受け取った場合 502
SERVICE_UNAVAILABLE サーバーがリクエストを処理できない場合 503
GATEWAY_TIMEOUT 上位サーバーからのレスポンスがタイムアウトした場合 504

https://trpc.io/docs/server/error-handling#error-codes

TRPCErrorクラスを使用してカスタムエラーを生成

tRPCでは、TRPCErrorクラスを使用してカスタムエラーを生成することができます。このクラスを使うことで、エラーコードやメッセージを指定し、クライアントに明確なエラー情報を返すことができます。

tRPCのプロシージャ内でエラーをスロー
server/api/routers/chat.ts
export const chatRouter = createTRPCRouter({
  hello: protectedProcedure
    .input(z.object({ text: z.string() }))
    .query(({ input }) => {
      if (input.text === "") {
        // input.textが空の場合、BAD_REQUESTエラーをスロー
        throw new TRPCError({
          code: "BAD_REQUEST",
          message: "Invalid text format",
        });
      }

      return {
        greeting: `Hello ${input.text}`,
      };
    }),
});

例えば、input.textが空の場合、BAD_REQUESTエラーをスローします。

Server Components内でエラーハンドリング

エラーハンドリング付きのtRPCプロシージャ呼び出し関数を作成します。

app/test-error/page.tsx
import { api } from "@/trpc/server";
import { TRPCError } from "@trpc/server";

~~
~~
// エラーハンドリング付きのtRPCプロシージャ呼び出し関数
const greeting = async (req: {
  text: string;
}): Promise<{ greeting: string | null; errorMessage: string | null }> => {
  try {
    // tRPCのプロシージャを呼び出す
    const response = await api.chat.hello(req);
    return { greeting: response.greeting, errorMessage: null };
  } catch (error) {
    console.log(error);

    let errorMessage = "不明なエラーが発生しました。";
    if (error instanceof TRPCError) {
      switch (error.code) {
        case "BAD_REQUEST":
          errorMessage = "無効なリクエストです。";
          break;
        case "FORBIDDEN":
          errorMessage = "アクセスが拒否されました。";
          break;
        default:
          errorMessage = "予期せぬエラーが発生しました。";
          break;
      }
    }

    return { greeting: null, errorMessage };
  }
};

エラーはtry-catchを使用しハンドリングしています。
error instanceof TRPCErrorのように型を絞り込み、TRPCErrorが発生した場合は、そのエラーコードに基づいて適切なエラーメッセージを設定して返します。

エラーメッセージがあれば表示します。

app/test-error/page.tsx
export default async function Page() {
  const result = await greeting({ text: "" });
  return (
    <div>
      <p>Server Components</p>
      {result.errorMessage ? (
        <p>
          エラーが発生しました: <span>{result.errorMessage}</span>
        </p>
      ) : (
        <h2>{result.greeting}</h2>
      )}
    </div>
  );
}
Client Components内でエラーハンドリング

Client Componentsでのフェッチでは内部でTanstack Queryを利用しています。

https://trpc.io/docs/client/react

components/ClientError.tsx
"use client";
import { api } from "@/trpc/react";

export function ClientError() {
  const { data, error } = api.chat.hello.useQuery({ text: "" });

  // ユーザーに表示するためのエラーメッセージを決定
  let errorMessage = "";
  if (error) {
    switch (error.data?.code) {
      case "BAD_REQUEST":
        errorMessage = "無効なリクエストです。";
        break;
      case "FORBIDDEN":
     errorMessage = "アクセスが拒否されました。";
     break;
      default:
        errorMessage = "予期せぬエラーが発生しました。";
        break;
    }
  }

  return (
    <div>
      <p>Client Components</p>
      <p>{data?.greeting}</p>
      {errorMessage && <p>{errorMessage}</p>}
    </div>
  );
}

Tanstack QueryのuseQueryは、データのフェッチ、キャッシュ、エラーハンドリングを自動的に行います。そのため、errorプロパティには、リクエストが失敗した際のエラーオブジェクトが格納されます。
ユーザーに表示するためのエラーメッセージを決定してから表示します。

Client Components呼び出しでもサーバーサイドでエラーハンドリングを行いたい場合

クライアントサイドにはエラーが返ってきますが、サーバーサイドでエラーをログに出すなどしたい場合は、Route Handler内でハンドリングすることができます。

app/api/trpc/[trpc]/route.ts
import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
import { type NextRequest } from "next/server";

import { appRouter } from "@/server/api/root";
import { createTRPCContext } from "@/server/api/trpc";

const createContext = async (req: NextRequest) => {
  return createTRPCContext({
    headers: req.headers,
  });
};

const handler = (req: NextRequest) =>
  fetchRequestHandler({
    endpoint: "/api/trpc",
    req,
    router: appRouter,
    createContext: () => createContext(req),
    onError: ({ error, type, path, input, ctx, req }) => {
      if (error.code === "BAD_REQUEST") {
        // エラーログをレポートする処理など
     ...
      }
    },
  });

export { handler as GET, handler as POST };

Client Componentsで呼び出す場合、tRPCのプロシージャで発生したすべてのエラーは、クライアントに送信される前にonErrorメソッドを通過します。したがって、ここでもエラーハンドリングを行うことができます。
https://trpc.io/docs/server/error-handling#handling-errors

⑥tRPCのmiddleware

tRPCのmiddlewareはプロシージャが呼び出される前に実行されるため、APIリクエストやバックエンドの操作に対して、特定の権限チェックやバリデーションを行うことができます。例えば、データの作成、更新、削除といった重要な操作や、機密情報へのアクセスを制限する場合に、tRPCのmiddlewareは非常に有効です。
https://trpc.io/docs/server/middlewares

adminProcedureを作成

管理者権限を持つかどうかをチェックするプロシージャを作成します。
t.procedure.use()メソッドを使用することでプロシージャにmiddlewareを追加することができます。

server/api/trpc.ts
export const adminProcedure = t.procedure
  .use(({ ctx, next }) => {
    // 権限チェック
    if (!ctx.session || !ctx.session.user) {
      throw new TRPCError({ code: "UNAUTHORIZED" });
    }
    return next({
      ctx: {
        // infers the `session` as non-nullable
        session: { ...ctx.session, user: ctx.session.user },
      },
    });
  });

アクセス制限が必要なAPIエンドポイントのみに適用するためのadminProcedureを作成します。
ctx.sessionのようにコンテキスト情報にアクセスすることができるのでそこから権限チェックを行います。権限がなければUNAUTHORIZEDエラーをスローします。
コンテキストについての説明はこちらの記事で紹介しています。

adminProcedureを使用しプロシージャを定義する

アクセス制限が必要なAPIエンドポイントのみに作成したadminProcedureを適用します。権限チェックを分離することで、プロシージャ内では独自のロジックのみに集中することができます。

server/api/routers/post.ts
export const chatRouter = createTRPCRouter({
  create: adminProcedure
  .input(z.object({ name: z.string().min(1) }))
  .mutation(async ({ ctx, input }) => {
    return ctx.db.post.create({
      data: {
        name: input.name,
        createdBy: { connect: { id: ctx.session.user.id } },
      },
    });
  }),
});

まとめ

今回はエラーハンドリングについて深く考えてみました。
エラーハンドリングは、単なるエラーメッセージの表示だけでなく、ユーザー体験を向上させたり、デバッグやメンテナンスの効率化にもつながります。効果的なエラーハンドリングを実装することで、開発や運用の質を向上させることができます。
最後までお読みいただきありがとうございました!少しでもお役に立てたら嬉しいです!

イベント告知! Generative AI/LLM Engineer Career Meetup #2

次世代のAI技術を活用し、キャリアアップを目指しているエンジニアの皆さんに向けて、第二回目となる「Generative AI/LLM Engineer Career Meetup」を開催します!🎉
実際の現場でのGoogle CloudやVertex AI/Geminiの活用事例について、第一線で活躍する専門家たちが現場で得られた知見を通じてお話しします。

  • Google Cloudを活用している or 活用したい生成AI/LLM分野のエンジニア
  • AI/LLM開発界隈のエンジニア同士の繋がりを作りたい方
  • AI技術に少しでも興味ある方

ぜひお気軽にご参加ください!

イベント詳細、お申し込みはconnpassから↓↓↓↓↓↓
https://blueish.connpass.com/event/329186/

皆様のご参加をお待ちしております!

Discussion