Getting Started
Blocks
This component is a custom implementation following the Build UI article.
About
The Server Toast component is a complete notification system for React Server Components that uses cookies, useOptimistic
, and Sonner to display toast messages from Server Actions.
Features
- ✅ Toast from Server Actions: Trigger toasts directly from your server actions
- ✅ Four toast types: success, error, warning, info
- ✅ Cookie persistence: Toasts survive redirects and page reloads
- ✅ Instant dismissal with useOptimistic
- ✅ Modern Sonner interface that's accessible
- ✅ Complete TypeScript support
- ✅ Zod validation for data security
Preview
Server Toast
Display toast notifications from server actions
"use client"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
export function ServerToastDemo() {
const handleSuccess = () => {
toast.success("Success! Your action was completed.")
}
const handleError = () => {
toast.error("Error! Something went wrong.")
}
const handleInfo = () => {
toast.info("Info: This is an informational message.")
}
const handleWarning = () => {
toast.warning("Warning: Please review this action.")
}
return (
<Card className="p-6">
<div className="mb-6">
<h3 className="text-lg font-semibold">Server Toast</h3>
<p className="text-muted-foreground text-sm">
Display toast notifications from server actions
</p>
</div>
<div className="grid gap-2">
<Button onClick={handleSuccess} variant="default" className="w-full">
Success Toast
</Button>
<Button onClick={handleError} variant="destructive" className="w-full">
Error Toast
</Button>
<Button onClick={handleInfo} variant="secondary" className="w-full">
Info Toast
</Button>
<Button onClick={handleWarning} variant="outline" className="w-full">
Warning Toast
</Button>
</div>
</Card>
)
}
Installation
1. Install the component
pnpm dlx shadcn@latest add https://ui.nowts.app/r/server-toast.json
2. Add the Toaster to your layout
import { Suspense } from "react"
import { ServerToaster } from "@/components/server-toast/server-toast.server"
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
{children}
<Suspense>
<ServerToaster />
</Suspense>
</body>
</html>
)
}
Usage
Basic usage
import { serverToast } from "@/components/server-toast"
export async function saveUserAction(formData: FormData) {
"use server"
try {
// Your save logic
const user = await saveUser(formData)
await serverToast("User saved successfully!", "success")
} catch (error) {
await serverToast("Failed to save user", "error")
}
}
All toast types
import { serverToast } from "@/components/server-toast"
// Success toast
await serverToast("Operation completed!", "success")
// Error toast
await serverToast("Something went wrong!", "error")
// Warning toast
await serverToast("Please check your input", "warning")
// Info toast (default)
await serverToast("Here's some information", "info")
// or simply
await serverToast("Here's some information")
With forms
import { redirect } from "next/navigation"
import { serverToast } from "@/components/server-toast"
async function createPostAction(formData: FormData) {
"use server"
const title = formData.get("title") as string
const content = formData.get("content") as string
if (!title || !content) {
await serverToast("Title and content are required", "warning")
return
}
try {
await createPost({ title, content })
await serverToast("Post created successfully!", "success")
redirect("/posts")
} catch (error) {
await serverToast("Failed to create post", "error")
}
}
export function CreatePostForm() {
return (
<form action={createPostAction} className="space-y-4">
<input name="title" placeholder="Post title" required />
<textarea name="content" placeholder="Post content" required />
<button type="submit">Create Post</button>
</form>
)
}
With API Routes
import { NextRequest } from "next/server"
import { serverToast } from "@/components/server-toast"
export async function POST(request: NextRequest) {
try {
const data = await request.json()
// Your API logic
await processData(data)
await serverToast("Data processed successfully!", "success")
return Response.json({ success: true })
} catch (error) {
await serverToast("Failed to process data", "error")
return Response.json({ error: "Failed" }, { status: 500 })
}
}
API
serverToast
function serverToast(
message: string,
type?: "success" | "error" | "warning" | "info"
): Promise<void>
Parameter | Type | Description | Default |
---|---|---|---|
message | string | The message to display | - |
type | "success" | "error" | "warning" | "info" | The toast type | "info" |
ServerToaster
Server component that reads toast cookies and displays them. Must be placed within a <Suspense>
boundary.
<Suspense>
<ServerToaster />
</Suspense>
Architecture
The Server Toast system works in 3 parts:
1. Toast creation (Server)
serverToast()
creates a cookie with a unique ID- Message and type are serialized to JSON
- Cookie expires after 24h
2. Toast reading (Server)
ServerToaster
reads all cookies starting with "toast-"- Validates data with Zod
- Creates server actions to delete each toast
3. Optimistic display (Client)
ClientToasts
usesuseOptimistic
for instant dismissal- Integrates with Sonner for UI
- Dismissing a toast triggers server action in background
Benefits
Cross-navigation persistence
Toasts survive:
- Server redirects
- Page reloads
- Tab navigation
- Browser close/reopen
Optimal performance
- Server-side rendering: No content flash
- Instant dismissal:
useOptimistic
for UX - Lazy loading: Toaster in
<Suspense>
Security
- HTTP-only cookies optional
- Zod validation of data
- No JS exposure of sensitive data
Advanced examples
With custom middleware
import { auth } from "@/lib/auth"
import { serverToast } from "@/components/server-toast"
export async function authenticatedAction(formData: FormData) {
"use server"
const user = await auth.getUser()
if (!user) {
await serverToast("Please sign in to continue", "warning")
redirect("/login")
return
}
// Authenticated action...
await serverToast(`Welcome ${user.name}!`, "success")
}
Conditional toast
import { serverToast } from "@/components/server-toast"
export async function updateUserAction(formData: FormData) {
"use server"
const changes = getChanges(formData)
if (changes.length === 0) {
await serverToast("No changes to save", "info")
return
}
const result = await updateUser(changes)
if (result.warnings.length > 0) {
await serverToast(
`Saved with ${result.warnings.length} warnings`,
"warning"
)
} else {
await serverToast("All changes saved successfully!", "success")
}
}
Complex notification system
import { serverToast } from "@/components/server-toast"
export async function bulkImportAction(formData: FormData) {
"use server"
const file = formData.get("file") as File
const results = await processBulkImport(file)
// Multiple messages based on result
if (results.errors.length > 0) {
await serverToast(
`${results.errors.length} items failed to import`,
"error"
)
}
if (results.warnings.length > 0) {
await serverToast(
`${results.warnings.length} items imported with warnings`,
"warning"
)
}
await serverToast(
`${results.success.length} items imported successfully!`,
"success"
)
}
Troubleshooting
Toasts don't appear
- Check that
<ServerToaster />
is in your layout - Make sure it's within a
<Suspense>
boundary - Verify that cookies are enabled
Duplicate toasts
- Toasts use unique IDs to prevent duplicates
- If you see duplicates, check that
<ServerToaster />
is only rendered once
Slow performance
- Toasts use
useOptimistic
for instant dismissal - Server-side deletion happens in the background
- If slow, check your cookie configuration
Source
Based on the excellent article by Build UI about toast messages in React Server Components.
On This Page
AboutFeaturesPreviewInstallation1. Install the component2. Add the Toaster to your layoutUsageBasic usageAll toast typesWith formsWith API RoutesAPIserverToastServerToasterArchitecture1. Toast creation (Server)2. Toast reading (Server)3. Optimistic display (Client)BenefitsCross-navigation persistenceOptimal performanceSecurityAdvanced examplesWith custom middlewareConditional toastComplex notification systemTroubleshootingToasts don't appearDuplicate toastsSlow performanceSourceBuild Your SaaS in Days, Not Months
NOW.TS is the Next.js 15 boilerplate with everything you need to launch your SaaS—auth, payments, database, and AI-ready infrastructure.
Learn more about NOW.TS