I’ve been working on a Next.js 14 application that supports two types of users: Admin and User. My goal is to have a globally accessible user state throughout my application without needing to run authentication checks on every page.
Previously, I used higher-order components (HOCs) to fetch the current user details and wrap my pages, but this method feels inefficient and seems to be running the same authentication logic on every page load.
I am considering using Context, Zustand, or Redux to manage the user state more effectively. My specific requirements are:
- Fetch user authentication details and user-specific data (e.g., roles) once, ideally on the server side
- Store these details globally so that all components/pages can access the user state.
- Minimize client-side rendering and data fetching to improve performance.
Previously, here’s what my implementation looked like using an HOC:
export default function isAuthenticated(Component: any) {
return async function IsAuthenticated(props: any) {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
if (!user) redirect("/?message=Not logged in");
const { data: userDetails, error: userDetailsError } = await supabase
.from("users")
.select("*")
.eq("id", user.id)
.single();
return <Component {...props} authUser={user} userDetails={userDetails} />;
};
}
I’ve tried implementing Zustand but where I continue to get stuck is how to access this server side without using useUser, making all of my pages client
// lib/store/user.ts
import { User } from "@supabase/supabase-js";
import { create } from "zustand";
interface UserState {
user: User | null;
setUser: (user: User | null) => void;
}
export const useUser = create<UserState>((set) => ({
user: null,
setUser: (user) => set({ user }),
}));
// lib/store/InitUser.tsx
"use client";
import { User } from "@supabase/supabase-js";
import { useEffect, useRef } from "react";
import { useUser } from "./user";
export default function InitUser({ user }: { user: User | null }) {
const initState = useRef(false);
const setUser = useUser((state) => state.setUser);
useEffect(() => {
if (!initState.current) {
setUser(user);
initState.current = true;
}
}, [user, setUser]);
return null;
}
// layout.tsx
export default async function UserLayout({
children,
}: {
children: ReactNode;
}) {
const supabase = createClient();
const {
data: { user },
} = await supabase.auth.getUser();
return (
<div className="flex h-screen">
<UserLeftSidebar />
<div className="flex flex-col flex-1">
<Navbar />
<section className="flex flex-1 flex-col overflow-hidden px-6 pb-6 pt-10 max-md:pb-14 sm:px-14">
<div
id="scrollableDiv"
className="mx-auto w-full max-w-7xl flex-1 overflow-scroll scrollbar-hide"
>
{children}
</div>
</section>
</div>
<InitUser user={user} />
</div>
);
}
Jay Hogan is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.