Next.js Discord

Discord Forum

Proper data loading strategies with layouts in app router

Answered
Asian black bear posted this in #help-forum
Open in Discord
Asian black bearOP
I was under the impression that you should move any data loading out of layouts and into child components using suspense boundaries. Doing this solved a lot of my fast refresh issues as well as slightly improving initial page load times.

I have a set of pages like so:
- /user/:username
- /user/:username/subpage
- /user/:username/another-sub-page

I'm wanting to create a common profile header block on each page. My first thought was two options:
1. Use a layout and load the minimal amount of data inside that layout server component. Wrap any data fetching in React cache() so it gets deduplicated when loading it in the page.
2. Continue what I'm doing now but use a singular <ProfileHeader /> component to share the code between the pages. Don't need to deduplicate data fetching but this will incur rerenders when transitioning pages.

Instinctively I want to go with option 1. The pages all load the same user profile based on the username param, but then load different relations depending on the page. If I were to use a layout and dedupe, I would need to split this query out into two: one to fetch the profile, one to then fetch relations of the profile.

Maybe I'm misunderstanding how cache() works, but by enabling logging in Drizzle, I can see that the page fetching the profile is actually executing the query again rather than having that function call deduplicated, rendering this approach basically useless, so it's actually doing this:
- layout fetches profile wrapped in React cache()
- page fetches profile again
- page fetches profile relations it needs

I can tolerate one level of waterfall due to route-level suspense using loading.tsx, but I'm not okay with doubling my queries like this. Is there a recommended strategy for approaching this?
Answered by Ray
i don't see you are using cache()?
View full answer

25 Replies

@Asian black bear I was under the impression that you should move any data loading out of layouts and into child components using suspense boundaries. Doing this solved a lot of my fast refresh issues as well as slightly improving initial page load times. I have a set of pages like so: - /user/:username - /user/:username/subpage - /user/:username/another-sub-page I'm wanting to create a common profile header block on each page. My first thought was two options: 1. Use a layout and load the minimal amount of data inside that layout server component. Wrap any data fetching in React `cache()` so it gets deduplicated when loading it in the page. 2. Continue what I'm doing now but use a singular <ProfileHeader /> component to share the code between the pages. Don't need to deduplicate data fetching but this will incur rerenders when transitioning pages. Instinctively I want to go with option 1. The pages all load the same user profile based on the username param, but then load different relations depending on the page. If I were to use a layout and dedupe, I would need to split this query out into two: one to fetch the profile, one to then fetch relations of the profile. Maybe I'm misunderstanding how `cache()` works, but by enabling logging in Drizzle, I can see that the page fetching the profile is actually executing the query again rather than having that function call deduplicated, rendering this approach basically useless, so it's actually doing this: - layout fetches profile wrapped in React `cache()` - page fetches profile again - page fetches profile relations it needs I can tolerate one level of waterfall due to route-level suspense using loading.tsx, but I'm not okay with doubling my queries like this. Is there a recommended strategy for approaching this?
are the layout and header using the same cached function? or you just creating two with cache()?
@Ray are the layout and header using the same cached function? or you just creating two with `cache()`?
Asian black bearOP
the same function imported from another file
@Asian black bear the same function imported from another file
it only run once for me, can you show some code how you use it?
Asian black bearOP
// layout.tsx
import { getUserByIdentifier } from "@/app/data-fetching";
import { PropsWithChildren } from "react";

type Props = PropsWithChildren<{
  params: {
    nickname: string;
  };
}>;

export default async function ProfileLayout({ children, params }: Props) {
  const profile = await getUserByIdentifier(params.nickname);

  return (
    <main className="container flex flex-col py-2">
      <h1 className="text-3xl font-bold">{profile.nickname}</h1>

      {children}
    </main>
  );
}


// page.tsx
import { getUserByIdentifier } from "@/app/data-fetching";

type Props = {
  params: { nickname: string };
};

export default async function UserPage({ params }: Props) {
  const user = await getUserByIdentifier(params.nickname);
  if (!user) notFound();

  return (
    <Suspense fallback={<UserLoading />}>
      <UserRenderer user={user} />
    </Suspense>
  );
}


// data-fetching.ts
import { fetchUserByIdentifier } from "@/lib/server/auth";
export const getUserByIdentifier = (identifier: string) => fetchUserByIdentifier(identifier);
hopefully thats somewhat clear
i don't see you are using cache()?
Answer
Asian black bearOP
lmao ok this serves me right for doing anything on christmas day
thanks for that
lol
Asian black bearOP
ok actually it still seems to fire twice

export const getUserByIdentifier = cache(
  async (identifier: string) => await fetchUserByIdentifier(identifier)
);
Asian black bearOP
i figure awaiting it would be necessary to have it memoize the actual result
yep
Asian black bearOP
so fetchUserByIdentifier shouldn't be triggering twice then, but it is
where do you use it?
Asian black bearOP
in the layout and page as before, the only thing i changed is making sure getUserByIdentifier() uses cache()
what version of next are you using?
Asian black bearOP
14.0.4, using turbopack if it makes a difference
not sure, I don't use turbo
maybe try without it
Asian black bearOP
still seems to trigger twice
actually i may be stupid
the second query was from the search i was doing to trigger the profile load
fresh reload and it only fires once. thanks for the help 🙂
oh lol