Why do nested layouts/pages render before their parent layouts? #53026
Replies: 10 comments 11 replies
-
@leerob This might be a good target for education content, unless I'm doing something wrong that is causing this rendering order. |
Beta Was this translation helpful? Give feedback.
-
I'm running into this problem when trying to set up a shared server context using I assumed that setting up a new storage context in the layout would make it visible to the page below it since it would be calling the Page's render function after the Layout's in the same callstack, but it seems like the page is called first and then the layout. That is really unexpected and should be documented, plus I can't find any other way to have an application-level "context" on the serverside that gets set up and run before all pages |
Beta Was this translation helpful? Give feedback.
-
@adamlong5, @ajwootto How is it going? Did you find any solution, guys? I am totally agree this thing is frustrating and that prevents to implement any auth logic using layouts.
const async function getAuthData() {
const isAuth = await auth();
if (isAuth) {
await doAuthLogic();
} else {
await doTempUserLogic();
}
}
What? Do I have to share the logic with pages? Ok: //app/project/onePage
const user = await getAuthData();
await projectFetch(); //app/project/anotherPage
const user = await getAuthData();
await projectFetch(); That looks good! (nope) But we have layout with user info. Ok, we has got //app/project/layout
const user = await getAuthData();
await projectFetch(); We must run the code every time we want to get the information related to our auth/project logic. THIS IS THE PROBLEM. But why is it doing this way? And, more importantly, how do we deal with it? |
Beta Was this translation helpful? Give feedback.
-
Soooo ? Is this by design and will never be fixed or this is a bug ? I am having the same issue. I used the layouts to "guard" against auth/not-auth users. I have AuthLayout -> OffersLayout -> OffersListPage/OffersMapPage. In the AuthLayout I get the user and redirect to login if there is no auth. In the OffersLayout I setup common controls for the Map/List pages like Search. In the Map/List page I show the offers in different ways. At the moment due to this bug I can not rely that the user is there. Please respond. |
Beta Was this translation helpful? Give feedback.
-
I'm currently experiencing this for my app, trying to force users to go through onboarding and the way I think would be best to do that is by checking the user's onboarding progress on the layout page, I have a similar code below: export default async function LoggedInUserLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const user = await getUser();
if (!user.onboardingCompleted)
return <Onboarding />;
return (
<>
<TopNav>
{children}
</>
);
} I think the only way to actually achieve this currently is by using HOC (higher order component) pattern which is pretty ugly. So basically:
export interface PageArgs {
params: { slug?: string };
searchParams: Record<string, string | undefined>;
}
export interface AuthProps {
user: User;
}
export default function withAuthGuard(
Component: React.FunctionComponent<AuthProps & PageProps>,
) {
return async (args: PageArgs) => {
const user = await getUser();
if (!user) return redirect('/sign-in');
if (!user.onboardingCompleted)
return redirect('/onboarding');
return (
<Component
user={user}
{...args}
/>
);
};
} Then on ALL your pages (and this is the annoying part because now you have to use this to all your pages otherwise it won't work as intended and all children component logic will still get executed)
function DashboardPage () {
// your logic here
return <Something />
}
export default withAuthGuard(DashboardPage) The reason why this problem occurs is because of how function calls work in the first place, React components are basically just functions that return html, and JSX is actually compiled into regular function calls, so: <Component1>
<Component2>
<Component3>
<h1>hello world</h1>
</Component3>
</Component2>
</Component1> Is actually compiled into something like this
and of course, by way of execution, the inner most function gets executed first and the return is passed into the function call above that, and so on. The problem stems from how we accept the children in the layout, we accept it as a
|
Beta Was this translation helpful? Give feedback.
-
I am experiencing a similar issue. I am doing some configuration in layout.tsx, for example for my axios instance, that is used in a page.tsx, but page.tsx is called before layout.tsx. Is there any file that is called before page.tsx, where I can put global configuration? I also found a similar issue: and I created my own: |
Beta Was this translation helpful? Give feedback.
-
Closest workaround I got is the HOC solution proposed by aprilmintacpineda (which is like a throwback to the class components era 😅 ). However, in my specific case, this just renders the In my case, we wanted a set of pages that are only available on non-production builds. We expected // (exclude-prod)/layout.tsx
const ExcludeProdLayout = ({ children }) => {
if (process.env.NEXT_PUBLIC_VERCEL_ENV === "production") {
notFound();
}
return children;
}; // (exclude-prod)/test-page/page.tsx
const TestPage = async () => {
await doSomeNonProductionOnlyWork(); // Crashes production builds
} Without an HOC, we need to simply duplicate the logic in all of our test pages, but then the // (exclude-prod)/test-page/page.tsx
const TestPage = async () => {
if (process.env.NEXT_PUBLIC_VERCEL_ENV === "production") {
notFound();
}
await doSomeNonProductionOnlyWork();
} |
Beta Was this translation helpful? Give feedback.
-
It's a design choice. I had same issues, cause intuitively I put my shared fetching and logic at layout server component but the doc says: |
Beta Was this translation helpful? Give feedback.
-
We stumbled across this with next-intl. Their docs advices you to check for a valid locale in the app/[locale]/layout.tsx file and throw an early (you'd think) notFound() if invalid or missing. I didn't expect our app/[locale]/[[...slug]]/page.tsx and its child server components to be run first. Makes no sense that you try to escape early in the layout by returning a 404 if the locale is invalid or missing, but that next goes through all the child components first, fetches its data etc... If we weren't so invested in Next already, this would be reason enough to switch to svelte or some other framework. |
Beta Was this translation helpful? Give feedback.
-
Is there a way to disable this behavior? |
Beta Was this translation helpful? Give feedback.
-
Imagine I have this simple nested setup in NextJS's app router:
If you added a simple console log to the two layouts and the inner page, you would expect the order of your logs to show:
That would match the waterfall paradigm of React, and the React component tree shown in the Component Hierarchy docs.
However, when I test this logging, I get the reverse:
Besides breaking the intuition of React with this order of execution, it also makes it hard to use layouts to do something like check if the user is logged in and redirect them if they're not — I'd assume the parent layout could do that check, and then the inner layout wouldn't have to worry about it, but that's not the case here.
Am I thinking about this in the wrong way?
Beta Was this translation helpful? Give feedback.
All reactions