🐢

Next.jsでフラッシュメッセージを実装する

2025/03/02に公開

これは何?

  • タイトルの通り。サーバーアクションの結果をフロントに伝達するためにフラッシュメッセージを実装したときのメモ
  • 環境
    • Next.js 15.2.0 App Router
    • shadcn/ui
  • 以前の記事をブラッシュアップして、前回より汎用的に使えるようにしました。

ユースケース

Ruby on Railsと同じ要領で、サーバー側のアクション(今回はReact Server Action)が実行されたら、期限0秒のcookieを設定し、フロント側にトーストメッセージを出す。

動作デモ

コード設計

レイアウトに配置するコンポーネント

flash-toaster.tsx
import { Toaster } from "@/components/ui/sonner";
import { cookies } from "next/headers";
import { FlashToasterClient } from "./flash-toaster/flash-toaster-client";

export type Flash = {
  type: "success" | "error";
  message: string;
};

export async function setFlash(flash: Flash) {
  (await cookies()).set("flash", JSON.stringify(flash), {
    path: "/",
    maxAge: 0,
  });
}

export default async function FlashToaster() {
  const flash = (await cookies()).get("flash");

  return (
    <FlashToasterClient flash={flash?.value}>
      <Toaster />
    </FlashToasterClient>
  );
}
  • maxAge: 0にすることで、すぐ期限切れになるようcookieを設定
  • FlashToasterClientuse clientを付けてクライアントコンポーネントにする。cookieの値は非同期で取得するため、paramsとして渡す。
  • クライアントコンポーネントのchildrenToasterを渡すことで、childrenをサーバーコンポーネントとして定義する(参考) ※ レンダリングパフォーマンス向上のためと、フラグメントを使いたくなかったため。

フラッシュの表示を担うコンポーネント

flash-toaster-client.tsx
"use client";

import { ReactNode, useEffect } from "react";
import { toast } from "sonner";
import { Flash } from "@/lib/flash-toaster";

export function FlashToasterClient({
  flash,
  children,
}: {
  flash: string | undefined;
  children: ReactNode;
}) {
  useEffect(() => {
    if (!!flash) {
      const data: Flash = JSON.parse(flash);
      switch (data.type) {
        case "success":
          toast.success(data.message);
          break;
        case "error":
          toast.error(data.message);
          break;
        default:
          break;
      }
    }
  });
  return children;
}
  • Flash型をJSON.parse()の結果に指定してanyを回避

レイアウトファイルに配置

layout.tsx
import "@/app/ui/global.css";
import { inter } from "@/app/ui/fonts";
import FlashToaster from "@/lib/flash-toaster";
import { ReactNode } from "react";

export default async function RootLayout({
  children,
}: {
  children: ReactNode;
}) {
  return (
    <html lang="en">
      <body className={`${inter.className} antialiased`}>
        {children}
        <FlashToaster />
      </body>
    </html>
  );
}
  • メインのコンポーネントをレイアウトに配置します。

フラッシュの設定

actions.ts
"use server";

import { setFlash } from "@/lib/flash-toaster";

export async function deleteInvoice(id: string, _prevState: unknown) {
  await prisma.invoices.delete({
    where: { id: id },
  });
  await setFlash({ type: "success", message: "delete invoice successful." });
  revalidatePath("/dashboard/invoices");
}
  • サーバーアクションの実行後に表示したいフラッシュメッセージのtypemessageを指定します。

感想

こちらの記事を参考に実装しました。無理な実装にならずに出来てよかったです。

Discussion