So i have been working on a Nextjs project recently, but there seems to be a problem with it while building. While trying to host it on vercel some of the pages are automatically generated in ISR mode despite being client components.
One potential cause i can infer for this is, as i am using react query for client side data fetching in my components. But i have not been able to find out any workaround to make this work.
Any help is deeply appreciated!
Below is the code of one of the pages being affected. All of these pages are also wrapped in a layout component. You can also view the code for it below.
"use client";
import { useEffect, useState } from "react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { useToast } from "@/hooks/use-toast";
import { Plus, Search, Loader2 } from "lucide-react";
import { Skeleton } from "@/components/ui/skeleton";
import { Club, User } from "@prisma/client";
import { ClubCard } from "@/components/clubs/ClubCard";
import { CreateClubDialog } from "@/components/clubs/CreateClubDialog";
import { EditClubSection } from "@/components/clubs/ClubDialog";
export default function ClubsPage() {
const [search, setSearch] = useState("");
const [dialogOpen, setDialogOpen] = useState(false);
const [selectedClub, setSelectedClub] = useState<
(Club & { clubLeads: string[] }) | undefined
>();
const { toast } = useToast();
const queryClient = useQueryClient();
// Fetch clubs
const { data: clubs, isLoading: isLoadingClubs } = useQuery({
queryKey: ["clubs", "all"],
queryFn: async () => {
const response = await fetch("/api/clubs/getAll");
if (!response.ok) throw new Error("Failed to fetch clubs");
const data = await response.json();
console.log(data.clubs)
return data.clubs;
},
});
// Fetch users for leader selection
const { data: users } = useQuery({
queryKey: ["users"],
queryFn: async () => {
const response = await fetch("/api/user/getAll");
if (!response.ok) throw new Error("Failed to fetch users");
const dat = await response.json();
return dat.users;
},
});
const userOptions =
users?.map((user: User) => ({
value: user.userId,
label: `${user.username} (${user.email})`,
})) ?? [];
useEffect(()=>{
console.log(selectedClub)
},[selectedClub])
// Create/Update club mutation
const clubMutation = useMutation({
mutationFn: async (data: Partial<Club>) => {
const response = await fetch(
selectedClub ? `/api/clubs/${selectedClub.clubId}` : "/api/clubs",
{
method: selectedClub ? "PUT" : "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
}
);
if (!response.ok) throw new Error("Failed to save club");
return response.json();
},
onSuccess: () => {
toast({
title: `Club ${selectedClub ? "updated" : "created"} successfully`,
});
setDialogOpen(false);
setSelectedClub(undefined);
queryClient.invalidateQueries({ queryKey: ["clubs"] });
},
onError: () => {
toast({
title: `Failed to ${selectedClub ? "update" : "create"} club`,
variant: "destructive",
});
},
});
const filteredClubs = clubs?.filter((club: Club) =>
club.clubName.toLowerCase().includes(search.toLowerCase())
);
return (
<>
<div className="container mx-auto py-8 px-8">
<div className="flex justify-between items-center mb-8">
<h1 className="text-3xl font-bold">Clubs Management</h1>
<CreateClubDialog />
</div>
<div className="relative mb-6">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
<Input
className="pl-10"
placeholder="Search clubs..."
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
{isLoadingClubs ? (
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{[...Array(6)].map((_, i) => (
<Skeleton key={i} className="h-[200px] rounded-lg" />
))}
</div>
) : (
<div className="flex flex-wrap gap-6">
{filteredClubs?.map((club: Club & {clubLeads:string[]}) => (
<ClubCard
key={club.clubId}
club={club}
leaders={club.clubLeads}
onClick={() => {
setSelectedClub({ ...club}); // Add actual clubLeads data
setDialogOpen(true);
}}
/>
))}
</div>
)}
</div>
{selectedClub && <EditClubSection
club={selectedClub as Club & { clubLeads: string[] }}
onClose={() => setSelectedClub(undefined)}
userOptions={userOptions}
isOpen={!!selectedClub}
/>}
</>
);
}
Layout.tsx
"use client";
import React, { Suspense, useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import { signOut, useSession } from "next-auth/react";
import { useToast } from "@/hooks/use-toast";
import { useRecoilState, useSetRecoilState } from "recoil";
import { userAtom } from "@/states/userAtom";
import { teamAtom } from "@/states/teamAtom";
import { Bell, Menu } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import { ModeToggle } from "@/components/theme-toggle";
import { NavigationLinks } from "@/components/layout/navigation-links";
import { TeamSelector } from "@/components/layout/team-selector";
import { UserMenu } from "@/components/layout/user-menu";
import { useQuery } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools/build/modern/production.js";
import { Team, User } from "@prisma/client";
import { permissionAtom } from "@/states/permissionAtom";
import { getDynamicPermissions } from "@/permissionManager/permissions";
import { NotificationPopover } from "@/components/notifications/NotificationPopOver";
import { notificationAtom } from "@/states/notificationAtom";
export default function DashboardLayout({
children,
}: {
children: React.ReactNode;
}) {
const session = useSession();
const router = useRouter();
const { toast } = useToast();
const [teams, setTeams] = useState<Team[] | null>(null);
const setUser = useSetRecoilState(userAtom);
const [currentTeam, setCurrentTeam] = useRecoilState(teamAtom);
const [isLoading, setIsLoading] = useState(false);
const [permission, setPermissions] = useRecoilState(permissionAtom);
const [notifications, setNotifications] = useRecoilState(notificationAtom)
//also fetch the user and store the permissions in the permissionsAtom
const fetchUser = async () => {
try {
const response = await fetch(
`/api/user/get?userId=${session.data?.userId}`
);
const data: {
user: { permissions: string[]; teams: Team[]; teamLeader: Team[] };
} = await response.json();
if (data.user) {
console.log(data);
const generatedPermissions = await getDynamicPermissions(
data.user.teams,
data.user.teamLeader
);
console.log("Generated Permissions: ", generatedPermissions);
setPermissions([
...data.user.permissions,
...(generatedPermissions || []),
]);
setTeams(data.user.teams);
if (data.user.teams.length > 0) {
setCurrentTeam(data.user.teams[0].teamId);
}
return data.user;
}
} catch (err) {
console.log("Error Fetching User Profile!", err);
toast({
title: "Error Fetching User Profile",
description: "Slow Internet Maybe!",
});
}
};
const fetchNotifications = async ()=>{
try
{
const response = await fetch(`/api/notification/getAll?userId=${session.data?.userId}`);
const data = await response.json()
setNotifications(data.notifications)
return data.notifications
}
catch(err)
{
console.log(err)
toast({
title:"Error Fetching Notifications!",
description:"Try Again!",
variant:"destructive"
})
}
}
const userQuery = useQuery({
queryKey: ["user", session.data?.userId],
queryFn: fetchUser,
staleTime: 1000 * 60 * 5,
refetchInterval: 1000 * 60 * 5,
});
const notificationsQuery = useQuery({
queryKey:['notifications', session.data?.userId],
queryFn:fetchNotifications,
staleTime: 5 * 60 * 1000,
refetchInterval: 5 * 60 * 1000,
})
useEffect(() => {
if (session.status === "unauthenticated") {
router.push("/auth/signin");
toast({
title: "Please Login Again!",
description: "Session Expired!",
});
}
}, [session.status]);
useEffect(() => {
// @ts-ignore
window.toggleDevtools = () => setShowDevtools((old) => !old);
}, []);
const handleLogOut = async () => {
try {
await signOut({ redirect: false });
router.push("/auth/signin");
toast({
title: "Logged Out",
description: "You have successfully logged out.",
});
} catch (error) {
toast({
title: "Logout Failed",
description: "Something went wrong while logging out. Try Again!",
variant: "destructive",
});
}
};
return (
<>
<div className="grid min-h-screen w-full md:grid-cols-[220px_1fr] lg:grid-cols-[280px_1fr]">
{/* Sidebar */}
<div className="hidden border-r bg-background md:block">
<div className="flex h-full max-h-screen flex-col gap-2">
<div className="flex h-14 items-center border-b px-4 lg:h-[60px] lg:px-6">
{!userQuery.isError && (
<TeamSelector
teams={teams || []}
currentTeam={currentTeam}
onTeamChange={setCurrentTeam}
/>
)}
{userQuery.isError && (
<Button
variant={"outline"}
className="w-full mr-2"
onClick={() => {
userQuery.refetch();
}}
>
Try Again!
</Button>
)}
<div className="p-2">
<NotificationPopover />
</div>
</div>
<div className="flex-1 px-2 lg:px-4">
<NavigationLinks />
</div>
</div>
</div>
{/* Main Content */}
<div className="flex flex-col">
<header className="flex h-14 items-center gap-4 border-b bg-background px-4 lg:h-[60px] lg:px-6">
<Sheet>
<SheetTrigger asChild>
<Button
variant="outline"
size="icon"
className="shrink-0 md:hidden"
>
<Menu className="h-5 w-5" />
<span className="sr-only">Toggle navigation menu</span>
</Button>
</SheetTrigger>
<SheetContent side="left" className="flex flex-col bg-background">
<div className="flex gap-2 items-center">
<TeamSelector
teams={teams || []}
currentTeam={currentTeam}
onTeamChange={setCurrentTeam}
/>
<NotificationPopover />
</div>
<NavigationLinks className="mt-4" iconClassName="h-5 w-5" />
</SheetContent>
</Sheet>
<div className="w-full flex-1">
<ModeToggle />
</div>
<UserMenu onLogout={handleLogOut} />
</header>
<main className="flex flex-1 flex-col gap-4 p-4 lg:gap-6 lg:p-6 bg-background">
<div className="flex flex-1 justify-center rounded-lg shadow-sm">
{children}
</div>
</main>
</div>
</div>
<ReactQueryDevtools initialIsOpen />
</>
);
}