Baptiste
Baptiste

Reputation: 13

next 13 next/cookies not dynamic, middleware not accessed when page cashed

it's the first time I post here but i search many links and forums and never found an answer working for me... I am saving a jwt in the cookies and i am using a middleware in nextjs in order to check if the user is well authnticated and if its token is not expired yet. My problem is that Next JS automatically cashes all pages, so when i go for example to "/home/chat", the middleware is triggered, the route is secured, and the user can access the page only if he's connected, but if i go back then to this page, it is already cashed so it never triggers the middleware again, so the user can access it even if its token is not valid anymore and it causes some problems. I added as said in the documentation "export const dynamic = 'force-dynamic'" or "export const revalidate = 0", but it's the same. Besides I saw in the documentation that the use of the cookies() function witl automatically put the page in dynamic state, but whatever i'm doing the middleware is not triggered again, and even the console.log() in the page is triggered just the first time and never again. Here is my "/home/chat" page :

import { cookies } from "next/headers";
import Link from "next/link";
import styles from "@/styles/chatPage/ChatPage.module.css";
import ChatClient from "@/components/chatPage/ChatClient";
import Profile_Service from "@/services/Profile.service";

export default async function ChatPage() {
    let     token: string | undefined;
    let     myself: Profile & { avatar: Avatar };

    console.log("chat page");
    try {
        token = cookies().get("crunchy-token")?.value;
        if (!token)
            throw new Error("No token value");

        const profilService = new Profile_Service(token);
        myself = await profilService.getProfileAndAvatar();

    } catch (err) {
        console.log(err);
        return (
            <div className={styles.error}>
                <h2>Oops... Something went wrong!</h2>
                <Link href={"/home"} className={styles.errorLink}>
                    <p>Return to Home Page!</p>
                </Link>
            </div>
        )
    }

    return (
        <ChatClient token={token} myself={myself}/>
    )
}

Thanks for the time you took reading me and for any help !

So I saw that before we had to use the gerServerSideProps function but this was before the app directory of next 13. I saw this documentation "Good to know: cookies() is a Dynamic Function whose returned values cannot be known ahead of time. Using it in a layout or page will opt a route into dynamic rendering at request time." but it's still not dynamic, i dont know if it's a bug or just something i don't understand yet. Maybe it's something that I don't understand but when i say it's still not dynamic I mean that the console.log and other functions in the middleware are triggered just the first time i go to the Chat Page for example. Then when i come back there is no log and no action in the middleware...

What I expect is to go through this middleware befaore accessing evry pages :

import { verifyAuth } from "@/lib/auth/auth";
import { NextRequest, NextResponse } from "next/server";

export async function middleware(req: NextRequest) {
  const crunchyToken = req.cookies.get("crunchy-token")?.value;

  const verifiedToken =
    crunchyToken &&
    (await verifyAuth(crunchyToken).catch((err) => {
      console.log(err);
    }));
  const url = req.nextUrl;
  console.log(verifiedToken, url);

  if (
    verifiedToken &&
    verifiedToken.login &&
    req.nextUrl.pathname === "/home/create"
  ) {
    return NextResponse.redirect(new URL("/home", req.url));
  }

  if (
    verifiedToken &&
    !verifiedToken.login &&
    req.nextUrl.pathname.startsWith("/home") &&
    req.nextUrl.pathname !== "/home/create"
  ) {
    return NextResponse.redirect(new URL("/home/create", req.url));
  }

  if (req.nextUrl.pathname === "/" && !verifiedToken) {
    return NextResponse.redirect(new URL("/welcome", req.url));
  }

  if (req.nextUrl.pathname === "/" && verifiedToken) {
    return NextResponse.redirect(new URL("/home", req.url));
  }

  if (req.nextUrl.pathname.startsWith("/welcome") && verifiedToken) {
    return NextResponse.redirect(new URL("/home", req.url));
  }

  if (req.nextUrl.pathname.startsWith("/home") && !verifiedToken) {
    return NextResponse.redirect(new URL("/welcome", req.url));
  }
}

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - icon, images
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     */
    '/((?!api|icon|images|_next/static|_next/image|favicon.ico).*)',
  ],
};

The purpose of this middleware is to get the token, verify it, and allow people to get to the restricted pages or redirect them to the authentication page.

First Edit : Thanks a lot for your help, right now I'd like my middleware to be triggered every time but also my server components because they get the user data with its token in the cookies. As I said in the documentation it says that it would be dynamic if i use the cookies() function but right now they are compiled at the beginning and never run again.

Here is my verifyAuth function :

import { jwtVerify} from "jose";

export const verifyAuth = async (token: string) => {

    try {
        const verified = await jwtVerify(
            token,
            new TextEncoder().encode(process.env.JWT_SECRET),
        );
        
        return verified.payload;

    } catch (error) {
        throw new Error('Your token has expired');
    }
}

Besides, the

console.log("ChatPage")

in the ChatPage on server side is logged just the first time i go to this page for example, even if it needs to get the cookies. One of the solution I found is to add this client components inside my server components :

// Sample Refresher Component
// to be imported into SSR page
// needing a refresh after 
// next.js appDir Link nav
//

'use client'
import { useRouter } from 'next/navigation'
import { useEffect } from 'react'

export function Refresher() {
    const router = useRouter()

    useEffect(() => {
        router.refresh()
    }, [router])

    return <></>
}

This is a very ugly method that I would NOT like to do, because i'm currently coding a One Page Application, but it is the only way i found to run the middleware before accessing my pages after the first time...

Please someone could help me ?

Upvotes: 1

Views: 2450

Answers (1)

Daniel Craciun
Daniel Craciun

Reputation: 149

Yes, Next.js does indeed cache the pages upon visiting it for the first time. This means going back to it inherently will not re-run the middleware.

However, If you refresh the page then it will force a re-render of the page and hence re-run the middleware; you can use console.logs to check the middleware being run.

The middleware will 100% run when going to any of your routes under your config, so the problem must lie in your token logic or conditions.

Provide your verifyAuth code because the problem may lie there.

Upvotes: 0

Related Questions