| name | toast-notifications |
| description | Send a one-shot toast/flash message from a Next.js (App Router) Server Action to the client so it appears on the next page render — crucially, surviving a redirect(), which normally drops all response data. Works by writing a short-lived cookie that the next render reads, fires once, and clears. Library-agnostic plumbing (sonner, react-hot-toast, or any toast UI) — it does NOT build the toast component itself. Use this skill whenever a Server Action needs to give page-level feedback after a mutation, especially when it ends in redirect() or revalidatePath(): "show a success/error message after delete/create/ update", "toast after redirect", "notify the user after a server-side action", "flash message like Rails flash[:notice]", "message waiting on the destination/sign-in page after redirect", "setToastCookie", "wire up ToastHandler". Trigger even when the user names their toast library (e.g. "fire a sonner toast from a server action that redirects") or doesn't say "toast" at all but clearly needs post-redirect feedback. Do NOT use for: building or styling a toast/banner UI component (use a component skill), configuring a toast library's Toaster, inline per-field form validation errors (return fieldErrors instead), client-side loading/optimistic toasts during a fetch, persistent dismissible banners, browser/web-push notifications, or setting cookies for non-message state like theme. Bundles ready-to-copy source files under assets/.
|
| allowed-tools | Read, Write, Edit, Glob, Grep, Bash |
| argument-hint | show toast after action | toast after redirect | add toast system |
Toast Notifications
A cookie-based toast system that lets a Server Action send a one-shot message to the
client, surviving a redirect(). The server writes a short-lived cookie; the next page
render reads it, fires the toast, and clears the cookie so it shows exactly once.
It is library-agnostic and dependency-free (no js-cookie — it reads
document.cookie directly). You plug in whatever toast UI you already use.
Reference files (read as needed):
- architecture.md — how it works, cookie config, security, debugging
- patterns.md — when/how to use it + complete examples (redirect vs.
client-handled, validation, bulk, upload) and the do/don't list
Copy-into-project source lives under assets/lib/toast/.
A redirect response can't carry data back to the component, but a cookie survives the
navigation and auto-expires. See architecture.md for the full rationale,
cookie config, and security notes.
Installation (run when the toast system isn't present yet)
-
Check whether the project already has it. Search for setToastCookie or a
lib/toast/ (or similar) folder. If it exists, skip to Usage — don't duplicate it.
-
Copy the four bundled files from this skill's assets/lib/toast/ into the project
(a lib/toast/ folder is the conventional home, but anywhere works):
<project>/lib/toast/
├── constants.ts # cookie name + max-age (seconds)
├── types.ts # ToastType, ToastMessage
├── server/toast.cookie.ts # setToastCookie() — server-only
└── components/toast-handler.tsx # ToastHandler — client, reads cookie on route change
The files import each other with relative paths, so they work no matter what path
alias (@/, ~/, …) the project uses.
-
Wire your toast library into toast-handler.tsx (two marked steps inside the file):
replace REPLACE_WITH_YOUR_TOAST_LIBRARY with your import and adjust the switch if your
library lacks .warning / .info. Recommended: @szum-tech/design-system (exposes
.success / .error / .warning / .info); alternatives include sonner or
react-hot-toast.
-
Mount <ToastHandler /> once near the app root, alongside your toast library's
container, so it runs on every route change:
import { ToastHandler } from "@/lib/toast/components/toast-handler";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{children}
{/* <Toaster /> */}
<ToastHandler />
</body>
</html>
);
}
Adjust the import alias (@/) to match the project.
Usage
Use setToastCookie only when the action redirects — the response is discarded, so the
cookie carries the message to the destination page. When an action instead returns a result
to the component, skip the cookie and toast on the client from that result (simpler, no
round-trip). See patterns.md for the full decision rule.
"use server";
import { redirect } from "next/navigation";
import { setToastCookie } from "@/lib/toast/server/toast.cookie";
export async function createOrder(formData: FormData) {
const [error, order] = await createOrderInDb(formData);
if (error) {
return { success: false, error: "Couldn't create the order. Please try again." };
}
await setToastCookie("Order created!", "success");
redirect(`/orders/${order.id}`);
}
Toast types & duration
await setToastCookie("Saved!", "success");
await setToastCookie("Something went wrong", "error");
await setToastCookie("Please review your input", "warning");
await setToastCookie("New features available", "info");
await setToastCookie("This stays longer", "info", 10_000);
API
function setToastCookie(message: string, type?: ToastType, duration?: number): Promise<void>;
type ToastType = "success" | "error" | "info" | "warning";
type ToastMessage = { type: ToastType; message: string; duration?: number };
Key rules
- Cookie only when you redirect. If the action returns a result to the component, toast on
the client from that result instead — don't set the cookie (see
patterns.md).
- Set the cookie before
redirect() — code after redirect() never runs (it throws).
- One
<ToastHandler /> only — multiple mounts can fire a toast twice.
- No sensitive data in messages — the cookie is readable by client JS (
httpOnly: false)
and messages are user-facing. Keep them generic.
- Toasts are for page-level feedback, not inline field validation — return field errors
to the form for those.
See patterns.md for the full do/don't list and complete server-action examples.