Preventing flash of unauthenticated content in Next.js SSR with JWT authorization

Introduction

I am creating an SSR app. While implementing JWT authorization, I noticed the flashing of secure content (FOUC) that should not be shown to an unauthorized person. I wrote the backend on NestJS, and I have an API route for a refresh token where I GET the refresh token, check it, and then, if it is valid, I extend it and return the extended one to the frontend. It’s a basic refresh operation. On the client, in the useEffect of the AuthProvider, I make the request with the refresh token. I also have a state called loaded to watch for the response. AuthProvider is in my main layout.

Issue

Because my app uses SSR, the server renders the HTML and returns it faster than the client-side JavaScript starts working. Consequently, I see secure content for a few seconds before the router.replace redirect to the /sign-in path occurs. I have tried several solutions:

  1. Server Component Auth: I tried making the auth component a server component, but I couldn’t pass the refresh token (stored in cookies) from the server component back to the client component effectively, which broke the JWT flow.
  2. Validation Route: I tried a server component that calls a separate API route to just check the token (without extending it). This has two problems: users would have to sign in frequently (depending on refresh token lifetime), and making two requests (one server-side to check, one client-side to extend) is bad for optimization.

I am looking for a way to prevent this flash while maintaining the token refresh logic.

'use client' 

import { useEffect, useState } from "react";
import { AuthApi } from "@/features/auth";
import { useRouter } from "next/navigation";
import { useUserStore } from "@/entities/user/model/user-store";

export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
    const [loaded, setLoaded] = useState(false);
    const router = useRouter()
    const setUser = useUserStore(state => state.setUser)
    const user = useUserStore(state => state.id)


    useEffect(() => {
        const initAuth = async () => {
            try {
                const data = await AuthApi.refreshProxy();
                setUser(data.user)

                if (data.user.role !== 'admin') {
                    router.replace('/');
                    return;
                }

            } catch (e) {
                setUser(null)
                router.replace('/sign-in');
            } finally {
                setLoaded(true);
            }
        };

        initAuth();
    }, []);

    if (loaded)
        return children


    return <div>Loading...</div>;
};

Ohhhh nooo, the problem is solved by adding styles to my loading div… :sob::sob: I just didnt see this block, so I saw the HTML page… Every AI lied to me, that javascript doesn’t render on Server, so funny, fell very dumb

1 Like

I’m glad you found a solution. Thanks for coming back to share what worked for you!