Next.js Discord

Discord Forum

`Redirect` in Layout causing weird error

Answered
Peterbald posted this in #help-forum
Open in Discord
PeterbaldOP
version: Next.js 15.1.6

My simplified application structure is:

src/app/
       (admin)/admin-dashboard/layout.tsx
       (admin)/admin-dashboard/page.tsx
       (admin)/admin-dashboard/(crud)/...
       (dashboard)/layout.tsx
       (dashboard)/dashboard/...
       (dashboard)/workspace/...


I'm trying to redirect from admin-dashboard/layout.tsx to /dashboard, if the user is not an admin user for example.

however, i'm getting this error:
Answered by Peterbald
RTFM moment for me.
- https://vercel.com/guides/react-context-state-management-nextjs
- https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#using-context-providers

- avoid context providers unless absolutely necessary, and keep it as deep as possible
- react query/swr in context may need changes
View full answer

102 Replies

PeterbaldOP
Admin Dashboard Layout.tsx
const DashboardLayout = async ({ children }: { children: React.ReactNode }) => {
  const user = await currentUser();
  const { redirectToSignIn } = await auth();

  if (!user) {
    redirectToSignIn();
  }

  console.log("admin dashboard reached");

  redirect("/dashboard");

  return <>Test</>;
};


User Dashboard Layout.tsx
const DashboardLayout = async ({ children }: { children: React.ReactNode }) => {
  return <>Hello</>;
}
as soon as redirect("/dashboard") is reached, it throws the error in the screenshot uploaded.
PeterbaldOP
---
Actually, this is also happening on a server component with redirect.
Rose-breasted Grosbeak
seems to be client-component on server issue.
or conditional hooks (as the error says)
PeterbaldOP
the conditional hook is incorrect.
i don't have such thing in my code, as soon as i remove redirect(...) the problem goes away
Rose-breasted Grosbeak
code?
PeterbaldOP
which one specifically should I share?
Rose-breasted Grosbeak
Minimal Reproducible example
PeterbaldOP
I am not sure how I'm supposed to provide the MRE for this:


admin dashboard layout.tsx
"use server";

import { SidebarProvider } from "@/components/ui/sidebar";
import DashboardSidebar from "../components/admin-sidebar";
import { currentUser, auth } from "@clerk/nextjs/server";
import UserManager from "@/lib/managers/userManager";
import { redirect } from "next/navigation";

const DashboardLayout = async ({ children }: { children: React.ReactNode }) => {
  const user = await currentUser();
  const { redirectToSignIn } = await auth();

  if (!user) {
    redirectToSignIn();
    return;
  }

  const isAdmin = await UserManager.isAdmin(user.id);

  if (!isAdmin) {
    // TODO: Look into this later when the issue with redirects is resolved.
    console.log("OH I AM SENDING THE NON-ADMIN TO USER DASHBOARD");
    redirect("/dashboard");
  }

  return (
    <>...</>
  );
};

export default DashboardLayout;
user dashboard layout.tsx
"use server";

import { SidebarProvider } from "@/components/ui/sidebar";
import DashboardSidebar from "./components/sidebar";
import AgencyManager from "@/lib/managers/agencyManager";
import { currentUser, auth } from "@clerk/nextjs/server";
import UserManager from "@/lib/managers/userManager";
import { Suspense } from "react";
import FallbackSpinner from "@/components/site/fallback-spinner";
import { redirect } from "next/navigation";
import Onboarding from "../onboarding/page";

const DashboardLayout = async ({ children }: { children: React.ReactNode }) => {
  const user = await currentUser();
  const { redirectToSignIn } = await auth();

  if (!user) {
    redirectToSignIn();
    return;
  }

  const email = user.emailAddresses[0].emailAddress;

  const foundUser = await UserManager.findUser(email);
  if (!foundUser) {
    // User not found, lets create an account...
    await UserManager.createUser({
      id: user.id,
      email: email,
      firstName: user.firstName!,
      lastName: user.lastName!,
      avatarUrl: user.imageUrl,
    });
  }

  const isAdmin = await UserManager.isAdmin(user.id);
  if (isAdmin) {
    // redirect("/admin-dashboard");
  }

  const agencyMember = await AgencyManager.findUserAgency(email);

  if (!agencyMember) {
    // redirect("/onboarding");
  }

  const workspaces = await AgencyManager.findAndFilterWorkspaces(
    agencyMember.email
  );

  return (
    <> ... </>
  );
};

export default DashboardLayout;
it sends the console log with OH I AM SENDING... and then on the server it shows 200 for admin-dashboard and dashboard routes, and on the client i'm seeing the error as shown.
use server is the issue @Peterbald
you dont add "use server" to server components
you add them to server actions
different things
remove it
@Arinji use server is the issue <@654952737720631307>
PeterbaldOP
That still causes the error when attempting to redirect from the admin dashboard layout to user dashboard page.
even if I remove "use server" directive.
import { SidebarProvider } from "@/components/ui/sidebar";
import DashboardSidebar from "../components/admin-sidebar";
import { currentUser, auth } from "@clerk/nextjs/server";
import UserManager from "@/lib/managers/userManager";
import { redirect } from "next/navigation";

const DashboardLayout = async ({ children }: { children: React.ReactNode }) => {
  const user = await currentUser();
  const { redirectToSignIn } = await auth();

  if (!user) {
    redirectToSignIn();
    return;
  }

  const isAdmin = await UserManager.isAdmin(user.id);

  if (!isAdmin) {
    redirect("/dashboard"); // here it goes poof
  }

  return (
    <SidebarProvider
      style={
        {
          "--sidebar-width": "18rem",
          "--sidebar-width-mobile": "18rem",
        } as React.CSSProperties
      }
    >
      <DashboardSidebar>{children}</DashboardSidebar>
    </SidebarProvider>
  );
};

export default DashboardLayout;
what does the /dashboard code look like @Peterbald
PeterbaldOP
the layout.tsx or the page.tsx?
do one thing
remove the layout first, i want to see where the issue is coming from
just rename it
layout-test.tsx
Rose-breasted Grosbeak
Can you make an example repo, that I can just copy/download and try locally?
and see if redirecting still gives an error
+ op dosent know what to make a minimal repo of.. and neither do i
Rose-breasted Grosbeak
yea that's required
PeterbaldOP
as soon as i disabled the dashboard layout, it did redirect me to /dashboard, and now i get a diff error.
@Arinji + op dosent know what to make a minimal repo of.. and neither do i
Rose-breasted Grosbeak
yk, just the code that gives the error that I can run on my machine
PeterbaldOP
so that's a +
@Rose-breasted Grosbeak yk, just the code that gives the error that I can run on my machine
PeterbaldOP
if i really had any idea where to look i'd make an MRE 🤣
this has just caught me so off guard
yea
a different error is always a good error :D
@Peterbald if i really had any idea where to look i'd make an MRE 🤣
Rose-breasted Grosbeak
by stripping stuff out..
Remove clerk, remove the external auth object
PeterbaldOP
Unhandled Runtime Error

Error: useSidebar must be used within a SidebarProvider.

useSidebar
file:///E:/Programming/luminae/.next/static/chunks/src_434520._.js (498:15)
SidebarTrigger
oh ok lmao
bring back the layout
and send the code of it
hmm kk one sec, lemme go through it
one more check
PeterbaldOP
i've found a workaround, just make a client component and return that from the layout and let the client handle the redirect, but i dont want to be cheeky
comment out all the code in the page.tsx
and make it render like just a div or smthn
so we can check if the error comes from the page calling a component
PeterbaldOP
as soon as i bring back the dashboard layout.tsx code, it stays at /admin-dashboard path,
even if i comment out the dashboard's page.tsx code.
i assume this is now because of the useSidebar hook, which is "now" being called conditionally due to redirect, BUT here's the interesting thing:
this has always worked until just now...
oh wait
show me the sidebar code
are you doing something like router.push in it?
PeterbaldOP
no.
not in the sidebar.
anywhere in the page?
PeterbaldOP
nope.
not just router.push, check for router.
PeterbaldOP
nope.
just these 3 files.
hmm
PeterbaldOP
no where else.
i can try going to Next 15.1.13 which i was on for the longest and remember this redirect working
ok lets look at this from a diff angle
remove your sidebar stuff
just comment them
there is some client component breaking on rendering
PeterbaldOP
dashboard layout.tsx

const DashboardLayout = async ({ children }: { children: React.ReactNode }) => {
  const user = await currentUser();

  if (!user) {
    return;
  }

  return (
    <div>
      <h1>Dashboard Layout</h1>
      {children}
    </div>
  );
};


dashboard page.tsx

const Dashboard = async () => {
  return <>DASHBOARD WOW</>;
}


-=-=-=-=-=-

admin layout.tsx

const DashboardLayout = async ({ children }: { children: React.ReactNode }) => {
  const user = await currentUser();

  if (!user) {
    return;
  }

  const isAdmin = await UserManager.isAdmin(user.id);

  if (!isAdmin) {
    redirect("/dashboard");
  }

  return <>haha admin layout go brrr</>;
}


admin page.tsx
const Dashboard = async () => {
  return <>hahaha admin dashboard kills me</>;
};
-=-=-=-=-

with this, it still throws the same issue when redirecting from /admin-dashboard (layout.tsx) to /dashboard (layout / page.tsx)

(conditional hooks...)
console log user
PeterbaldOP
consists of some user metadata, email etc
so not null, because i'm logged in rn.
ok and after removing all that
you stil see the same error?
PeterbaldOP
correct.
the only workaround i've found is to use a client component which handles the redirect for now 🤣
"use client";

import { useRouter } from "next/navigation";
import type { Route } from "next";
import { useEffect } from "react";

export default function ClientRedirect({ href }: { href: Route }) {
  const router = useRouter();

  useEffect(() => {
    router.push(href);
  }, []);

  return null;
}
so i just do if (..) return <ClientRedirect href={...} />
i'm thinking of going back to 15.1.12 or 15.1.13 to test if the issue persists.
did downgrading not work?
PeterbaldOP
i think i might have found the issue.
so trying to see if that does resolve it.
PeterbaldOP
turns out, my NotificationsProvider context was f--cking up the thing.
which was part of my global app layout.
PeterbaldOP
which looked like this:
"use client";

import {
  fetchNotificationsAction,
  markAllAsReadAction,
  markAsReadAction,
} from "@/actions/notifications";
import { toast } from "@/hooks/use-toast";
import { useTranslations } from "next-intl";
import type React from "react";
import { useEffect, useMemo, useRef } from "react";
import useSWR from "swr";

export const NotificationsProvider: React.FC<{ children: React.ReactNode }> = ({
  children,
}) => {
  const t = useTranslations();

  const {
    data: notifications = [],
    isLoading: loading,
    mutate,
  } = useSWR(["notifications"], fetchNotificationsAction, {
    refreshInterval: 30_000,
    dedupingInterval: 1_000,
    revalidateOnFocus: false,
    revalidateOnReconnect: false,
    errorRetryCount: 2,
  });

  const previousUnreadCountRef = useRef(0);

  const unreadNotifications = useMemo(
    () => notifications.filter((n) => !n.read),
    [notifications]
  );
  const readNotifications = useMemo(
    () => notifications.filter((n) => n.read),
    [notifications]
  );
  const unreadCount = useMemo(
    () => (notifications).filter((n) => !n.read).length,
    [notifications]
  );

  useEffect(() => {
    if (unreadCount > previousUnreadCountRef.current) {
      toast(...);

      previousUnreadCountRef.current = unreadCount;
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [unreadCount]);

  const markAsRead = async (id: string) => {
    await markAsReadAction(id);
    mutate();
  };

  const markAllAsRead = async () => {
    await markAllAsReadAction();
    mutate();
  };

  return (
    <NotificationsContext.Provider
      value={{
        unreadNotifications,
        readNotifications,
        unreadCount,
        markAsRead,
        markAllAsRead,
        loading,
      }}
    >
      {children}
    </NotificationsContext.Provider>
  );
};
Thank you again for your help mate, cheers!
Rose-breasted Grosbeak
And that's why creating an MRE is important
@Peterbald Thank you again for your help mate, cheers!
Write down what fixed the issue, and mark that as an answer :D
someone in the future might have the same issue :D
PeterbaldOP
RTFM moment for me.
- https://vercel.com/guides/react-context-state-management-nextjs
- https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns#using-context-providers

- avoid context providers unless absolutely necessary, and keep it as deep as possible
- react query/swr in context may need changes
Answer