next.js

How to show a toast message after redirect?


I'm using Next.js latest version (App Router) and I want to show a toast (flash) message after a server-side redirect. Here's my use case:

I have a server component (e.g., /login/page.tsx) that checks if a user is already logged in. If they are, I want to:

  1. Redirect them to /
  2. Show a toast like: "You are already logged in"

I'm using react-hot-toast, which only works in client components, so I can't trigger the toast directly from the server.

What I tried: I've tried setting a cookie and remove it once read it from a client component. But that doesn't work:

cookieStore.set() throws and error: Cookies can only be modified in a Server Action or Route Handler.

// /login/page.tsx

    import styles from "@/app/styles/auth.module.css";
    import LoginComponent from "@/app/components/Auth/LoginComponent";
    import { redirect } from "next/navigation";
    import { getUserSession } from "@/lib/session/session.server";
    import { setToastMessage } from "@/lib/toast-message.server";
    
    async function LoginPage() {
        const session = await getUserSession();
    
        if (session) {
            await setToastMessage("You are already logged in.", "error");
            return redirect("/");
        }
    
        return (
            <div className={styles.container}>
                <div className={styles.content}>
                    <div className={styles.header}>
                        <span>Login</span>
                    </div>
                    <div className={styles.main}>
                        <LoginComponent />
                    </div>
                </div>
            </div>
        );
    }
    
    export default LoginPage;


// @/lib/session/session.server.ts

    import { cookies } from "next/headers";
    
    export async function setToastMessage(message: string, type: string) {
        const cookieStore = await cookies();
    
        cookieStore.set("_toast", message);
        cookieStore.set("_toast_type", type);
    }


// Here `cookieStore.set()` throws and error:
Cookies can only be modified in a Server Action or Route Handler.

// /page.tsx

    import Dashboard from "./components/Dashboard/DashBoard";
    import ToastServerMessage from "./components/ToastServerMessage";
    
    export default function Home() {
        return (
            <>
                <Dashboard />
                <ToastServerMessage />
            </>
        );
    }

// ToastServerMessage.tsx

    "use client";
    
    import { useEffect } from "react";
    import cookies from "js-cookie";
    import toast from "react-hot-toast";
    
    function ToastServerMessage() {
        useEffect(() => {
            const toastMessage = cookies.get("_toast");
            const toastType = cookies.get("_toast_type");
    
            if (toastMessage && toastType) {
                if (toastType === "error") {
                    toast.error(toastMessage);
                }
            }
            
            // delete cookie
            // ...
        }, []);
    
        return <></>;
    }
    
    export default ToastServerMessage;

What I need:

How can I safely:

  1. Redirect from a server component
  2. Set a temporary message
  3. And have a client component show that message as a toast on the redirected page?

Thanks in advance!


Solution

  • Best Thing is to Use a Route Handler to Set the Cookie, Then Redirect
    Create a Route Handler:

    import { NextResponse } from 'next/server';
    
    export async function GET() {
      const response = NextResponse.redirect(new URL('/', process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000'));
    
      response.cookies.set('_toast', 'You are already logged in', {
        path: '/',
        httpOnly: false,
        secure: true,
        sameSite: 'strict',
      });
    
      response.cookies.set('_toast_type', 'error', {
        path: '/',
        httpOnly: false,
        secure: true,
        sameSite: 'strict',
      });
    
      return response;
    }
    

    Update your /login/page.tsx to redirect to this API route:

    import { redirect } from 'next/navigation';
    import { getUserSession } from '@/lib/session/session.server';
    
    export default async function LoginPage() {
      const session = await getUserSession();
    
      if (session) {
        return redirect('/api/redirect-login'); 
      }
    
      return (
       <div className={styles.container}>
          <div className={styles.content}>
            <div className={styles.header}>
              <span>Login</span>
            </div>
            <div className={styles.main}>
              <LoginComponent />
            </div>
          </div>
        </div>
      );
    }
    

    Client Component to Show Toast on Redirected Page.

    'use client';
    
    import { useEffect } from 'react';
    import toast from 'react-hot-toast';
    import cookies from 'js-cookie';
    
    export default function ToastServerMessage() {
      useEffect(() => {
        const toastMessage = cookies.get('_toast');
        const toastType = cookies.get('_toast_type');
    
        if (toastMessage && toastType) {
          if (toastType === 'error') toast.error(toastMessage);
          if (toastType === 'success') toast.success(toastMessage);
        }
    
        cookies.remove('_toast');
        cookies.remove('_toast_type');
      }, []);
    
      return null;
    }
    

    Include <ToastServerMessage /> in your main layout or home page where needed

    NextResponse lets you set cookies and redirect at once, js-cookie can read and clear those cookies and react-hot-toast shows the message once the client loads.

    Why this is best: