Next.js Discord

Discord Forum

middleware headers client navigation

Unanswered
Pteromalid wasp posted this in #help-forum
Open in Discord
Avatar
Pteromalid waspOP
middleware headers are not carried over during client-side navigations, how do i get the headers when navigating routes via Link? i'm using a client component wrapper in my root layout to get the headers via a server action but obviously it's not able to get them when navigating via the client (Link). using app router

"use client"

import React, { useState, useEffect } from 'react';
// import { signedIn } from '@/atoms/auth_atoms';
// import { useAtom } from 'jotai';
import { getSessionData } from '@/actions/auth_actions';
import { useAtom } from 'jotai';
import { telegramUserData, signedIn } from '@/atoms/auth_atoms';


export function SM({ children }) {
    const [isSignedIn, setSignedIn] = useAtom(signedIn)
    const [tud, setTud] = useAtom(telegramUserData)

    console.log('[SM] signed in:', isSignedIn)

    useEffect(() => {
        async function fetchData() {
            try {
                const { userData, sessionStatus } = await getSessionData();
                console.log('[SM] - userData', userData)
                console.log('[SM] - sessionStatus', sessionStatus)
                setTud(userData);
                setSignedIn(sessionStatus);
            } catch (error) {
                console.error("Failed to fetch session data:", error);
            } finally {
                // setLoading(false);
            }
        }
        console.log('[SM USEEFFECT CALL]')
        fetchData();
    }, []);

    return (
        <>
            {children}
        </>
    )
}


root:
<SM>
    {children}
</SM>


i just need to get the headers from middleware on every route change to be able to set some states / show toasts after the middleware session verification

55 Replies

Avatar
@Pteromalid wasp middleware headers are not carried over during client-side navigations, how do i get the headers when navigating routes via Link? i'm using a client component wrapper in my root layout to get the headers via a server action but obviously it's not able to get them when navigating via the client (Link). using app router "use client" import React, { useState, useEffect } from 'react'; // import { signedIn } from '@/atoms/auth_atoms'; // import { useAtom } from 'jotai'; import { getSessionData } from '@/actions/auth_actions'; import { useAtom } from 'jotai'; import { telegramUserData, signedIn } from '@/atoms/auth_atoms'; export function SM({ children }) { const [isSignedIn, setSignedIn] = useAtom(signedIn) const [tud, setTud] = useAtom(telegramUserData) console.log('[SM] signed in:', isSignedIn) useEffect(() => { async function fetchData() { try { const { userData, sessionStatus } = await getSessionData(); console.log('[SM] - userData', userData) console.log('[SM] - sessionStatus', sessionStatus) setTud(userData); setSignedIn(sessionStatus); } catch (error) { console.error("Failed to fetch session data:", error); } finally { // setLoading(false); } } console.log('[SM USEEFFECT CALL]') fetchData(); }, []); return ( <> {children} </> ) } root: <SM> {children} </SM> i just need to get the headers from middleware on every route change to be able to set some states / show toasts after the middleware session verification
Avatar
what does your middleware looks like?
how did you do the client-side navigation?
the code you've sent seems irrelevant to me
is it about how you can retrieve headers in client components?
Avatar
Pteromalid waspOP
middleware:

import { NextResponse } from 'next/server';
import { verifySession } from './actions/auth_actions';


export async function middleware(request) {
    const cookie = request.cookies.get('wac');
    console.log(`[Middleware] - Incoming request URL: ${request.url}`);

    if (!cookie) {
        // If no session cookie and trying to access a protected route:
        console.log('[Middleware] - No session cookie found');
        return NextResponse.redirect(new URL('/', request.url));
    }

    console.log(`[Middleware] - Found cookie: ${cookie}`);
    const { data: verifySessionData, error: verifySessionError } = await verifySession(cookie);

    if (verifySessionError) {
        console.log(`[Middleware] - Session invalid or expired: ${verifySessionError}`);
        // return NextResponse.redirect(new URL('/?sessionExpired=true', request.url));
        // return NextResponse.redirect(new URL('/', request.url));
        const response = NextResponse.redirect(new URL('/', request.url));
        response.headers.set("x-session-status", "expired");
        console.log('[Middleware] EXPIRED RESPONSE', response)
        return response;
    }

    console.log('[Middleware] - Session valid', verifySessionData);
    // return NextResponse.next();
    const response = NextResponse.next();
    response.headers.set("x-user-data", JSON.stringify(verifySessionData));
    response.headers.set("x-session-status", "valid");
    console.log('[Middleware] VALID RESPONSE', response)
    return response;
}

// const PROTECTED_PATHS = ['/screener/:path*'];
export const config = {
    matcher: ['/screener/:path*'],
};
Avatar
is the headers there in Page.js?
Avatar
@Alfonsus Ardani is it about how you can retrieve headers in client components?
Avatar
Pteromalid waspOP
yes via client side navigation. if i refresh then i can receive the headers from the server action call in SM but not when navigating via a button / Link
ex going from index -> protected route via Link, I receive no headers in SM. if i'm on the protected page and refresh, then i can get them
Avatar
when navigating, it will call server components. you can get your headers there and pass them into your client components
Avatar
Pteromalid waspOP
can u explain more or give an example
Avatar
when you navigate via button or <Link prefetch={false} /> to lets say /dashboard
it will call /dashboard/page.js

in that page.js, you can const headerStore = await headers() and get the header and pass it to your client component like
<SM session={headerStore.get('session')} />
Avatar
Pteromalid waspOP
i see. so i would have to wrap my returned items in each page with SM individually then?

ex:

/dashboard/pages.jsx

return (
<SM session={headerStore.get('session')}>
other components
</SM>
)


and same for every other page.jsx i want to receive headers in?
Avatar
yeah
headers dont naturally get forwarded to the Response of a request
when you set a header in middleware.ts, it will only modify the Request headers. which you can access later at page.js or layout.js but it will not be returned to the client
Avatar
Pteromalid waspOP
"which you can access later at page.js or layout.js"
so i can do this then instead of wrapping each individual return page.jsx correct?

root layout:
const headerStore = await headers()
...
<SM session={headerStore.get('session')}>
{children}
</SM>
or would it have to be each routes layout
or either?
Avatar
its not a good practice
because soft navigation allows you to not rerender the root layout
which is bad
unless ure okay with that
usually you only do that in root layout BUT you check if its authenticated at every page.js
so both of these needs to be true:
<Session>{children}</Session> at root layout

AND

const auth = await getSession()
if(!auth) redirect('/login')

at every protected page
Avatar
Pteromalid waspOP
ya i would have to change the middleware to verify on every page then i guess
but that would be fine right
Avatar
your middleware is already run on every page
but im just saying you dont need to wrap a SessionManagement context at every page. but instead, check auth session in the server at every page
so block unauthed user at the server before it even got to rendering the client
Avatar
@Pteromalid wasp ya i would have to change the middleware to verify on every page then i guess
Avatar
Pteromalid waspOP
misspoke for this, i mean't like change the matcher because my current one is

export const config = {
    matcher: ['/screener/:path*'],
};


only for the dashboard
but still u don't recommend wrapping SM in the root, where would i put it then for setting state on every page
Avatar
I dont recommend only checking auth in root. If you only wrap SM in root and still check auth at every page then its okay
Avatar
@Alfonsus Ardani because soft navigation allows you to not rerender the root layout
Avatar
Pteromalid waspOP
can u clarify this as well
Avatar
user access /dashboard
loads /layout.tsx
loads /dashboard/layout.tsx
loads /dashboard/page.tsx

user navigates to /dashboard/edit
loads /dashboard/edit/layout.tsx
loads /dashboard/edit/page.tsx
the root layout doesn't get rerendered or re-run
which is good
but you still need to do server auth check at every page
Avatar
Pteromalid waspOP
ur saying that if i have SM in root that it will not send new session headers to SM if i navigate to other routes?
Avatar
it will not
Avatar
Pteromalid waspOP
so instead put SM in each routes layout then instead of the root layout?
Avatar
thats one of the possible solution yeah
Avatar
Pteromalid waspOP
the other being in page.jsx? or is there another
Avatar
the other one is where you check auth at every page.jsx
because that is the recommended approach
must haves:
1. check auth at every page.js
2. check auth at every route.ts
3. check auth at every server actions

nice to have:
1. have client component wrap session at root layout for better client component control
2. naively check auth at middleware for front-line defense
Avatar
Pteromalid waspOP
forgot to reply the other day but thank you! appreciate the time u took to help me out

one thing i changed was i'm now using url params instead of the x-session-status header bc the headers don't persist for redirects, ex from a protected page -> index. was just getting null when trying to access them
Avatar
Pteromalid waspOP
i'll look into cookies but the url param is just for letting me know the session expired on redirect so i can show a toast, ex /?sessionExpired=true
Avatar
ahh
client detect that the session is expired -> await show toast -> redirect to ./login
Avatar
Pteromalid waspOP
i guess i could but that's what i was using the middleware for. verify session token, if not expired -> continue, else if on protected page -> redirect to index with sessionExpired url param otherwise continue with sessionExpired url param
Avatar
okay, i see
might as well
/?sessionExpired=true&next=/protectedPage so that user gets rediercted to the same page after logging in :D
Avatar
Pteromalid waspOP
wait wym