Next.js Middleware and Cookie Issue: Logout Not Clearing Cookie Consistently

Problem Statement

I’m working on a Next.js app where I need to manage user authentication with cookies. I have set up login, logout, and middleware files for handling JWT tokens and securing routes. However, I’m facing an issue where logging out does not always clear the cookie, and it seems inconsistent in detecting or removing the cookie in certain cases. The logout works seamlessly in localhost, but in Vercel deployment it doesn’t work.

Code Setup

Here’s my code for each relevant file:

  1. Login Route (login/route.ts)

    • This endpoint creates and sets a JWT token in the cookies when the user logs in.
    import { NextResponse, NextRequest } from "next/server";
    import jwt from 'jsonwebtoken';
    import { cookies } from 'next/headers';
    
    export async function POST(request: NextRequest) {
        try {
            const body = await request.json();
            const token = jwt.sign(body.user, 'secret', { expiresIn: '1d' });
            const isLocalhost = request.url.includes('localhost');
    
            cookies().set('token', token, {
                httpOnly: true,
                path: '/',
                sameSite: 'lax',
                secure: !isLocalhost,
                maxAge: 7 * 24 * 60 * 60,  // Adjusted for clarity
                ...(isLocalhost ? {} : { domain: 'swiftship-nine.vercel.app' })
            });
    
            return NextResponse.json({ message: "Success" }, { status: 200 });
        } catch (error) {
            return NextResponse.json({ message: "Error setting cookie" }, { status: 500 });
        }
    }
    
  2. Logout Route (logout/route.ts)

    • This endpoint is supposed to clear the JWT token from cookies.
    import { NextResponse, NextRequest } from "next/server";
    
    export function POST(request: NextRequest) {
        try {
            const response = NextResponse.json({ message: "Success" }, { status: 200 });
            response.cookies.set('token', '', { expires: new Date(0), path: '/' }); // Ensure matching path for deletion
            return response;
        } catch (error) {
            return NextResponse.json({ message: "Failed to log out" }, { status: 400 });
        }
    }
    
  3. Middleware (middleware.ts)

    • This file is responsible for verifying the JWT token for protected routes and redirecting accordingly.
    import { NextResponse } from 'next/server';
    import type { NextRequest } from 'next/server';
    import { jwtVerify } from 'jose';
    
    export async function middleware(request: NextRequest) {
        const { pathname } = request.nextUrl;
        const token = request.cookies.get("token")?.value;
        const secret = new TextEncoder().encode("secret");
        if (pathname.startsWith("/api/logout")) {
            // Clear the token cookie
            const res = NextResponse.next();
            res.cookies.set("token", "", { expires: new Date(0) });
        }
    
        // Check paths requiring logged-in user
        if (pathname.startsWith("/cart") || pathname.startsWith("/checkout") ||
            pathname.startsWith("/track") || pathname.startsWith("/profile")) {
    
            if (!token) return NextResponse.redirect(new URL('/login', request.url));
    
            try {
                const { payload } = await jwtVerify(token, secret);
    
                if (!payload || (payload.user_type !== 1 && pathname !== "/profile")) {
                    return NextResponse.redirect(new URL('/login', request.url));
                }
                return NextResponse.next();
            } catch (error) {
                return NextResponse.redirect(new URL('/login', request.url));
            }
    
        // Check paths for vendor/admin access
        } else if (pathname.startsWith("/vendor/") || pathname.startsWith("/vendorincoming") ||
                   pathname.startsWith("/admin")) {
    
            if (!token) return NextResponse.redirect(new URL('/', request.url));
    
            try {
                const { payload } = await jwtVerify(token, secret);
    
                if (!payload || pathname.replace("/admin/", "") !== payload.id || payload.user_type !== 2) {
                    return NextResponse.redirect(new URL('/', request.url));
                }
                return NextResponse.next();
            } catch (error) {
                return NextResponse.redirect(new URL('/login', request.url));
            }
    
        // Redirect logged-in users from login/signup pages
        } else if (pathname.startsWith("/login") || pathname.startsWith("/signup") || pathname.startsWith("/vendorob")) {
            if (token) return NextResponse.redirect(new URL('/', request.url));
        }
    
        return NextResponse.next();
    }
    
    // Middleware configuration for matching routes
    export const config = {
        matcher: [
            "/",
            "/cart",
            "/checkout",
            "/login",
            "/signup",
            "/track/:path*",
            "/menu/:path*",
            "/profile/:path*",
            "/admin/:path*",
            "/vendor/:path*",
            "/vendorincoming/:path*",
            "/vendorob",
        ],
    };
    

Issue

Despite setting expires: new Date(0) to clear the cookie in logout/route.ts and attempting to handle it in middleware, I sometimes notice:

  • The cookie doesn’t seem to be cleared immediately, leading to inconsistent behavior.
  • Protected routes don’t always redirect correctly after logging out, which suggests that the cookie might still exist momentarily.

What I’ve Tried

  • Verified the path in the cookie deletion matches the one used during setting.
  • Used both set with an empty value and expires: new Date(0) to delete the cookie.
  • Adjusted middleware to handle token verification errors and clear cookies conditionally.
  • Tested on both localhost and deployed environments to check for domain-specific behavior.

Question

What am I missing to ensure the cookie is consistently deleted upon logout? Could there be an issue with my middleware.ts handling or something specific to Next.js cookies? I have gone through different questions but they just point out how they the poster is deleting the request side cookie and not the response side cookie but I have considered that.

Any guidance on best practices for managing cookies in Next.js with middleware would be highly appreciated.