The high-level problem I'm trying to solve is, I have a hero component that has an "image" and some content:
<Hero>
<ImagePart />
<ContentPart />
</Hero>
The "image" part can kind of be anything (an image, a video, a slide-show carousel), but, significantly, a ThreeJS animation which needs to not re-render on page navigation, though some sub-components will need to update based on the page.
I have this working, by putting the whole Hero
component in a layout instead of a page, and then using usePathname
and context
to grab the correct per-page info, but the thing I'd like to solve is that the ContentPart
could/should be a server component / pre-rendered.
If someone can suggest an "architecture" change to my layout/page/data-fetching structure so I can accomplish this, it would be much appreciated.
Here's what I'm currently doing:
/app/layout.js // async; does CMS query to get all heroes for all pages
// also includes ThreeProvider and HeroProvider
(pages-no-hero) // route group for no hero at all
(pages)
layout.js // `Hero` component is here
page.js
Then I have
<Hero> // parent div, grid layout
<HeroThree>
<Model /> // lazy loads client side, must be child of ThreeProvider, must not re-render on navigation
// ... other components which use HeroProvider to update animation
</HeroThree>
<StaticHero> // *** this should be static / SSR, but is a child of layout, not page
<StaticImagePart /> // different per page; sits on top and hides HeroThree if it exists
<Content /> // different content per page
</StaticHero>
</Hero>
where StaticHero
is currently a client component that uses usePathname
and the context from the HeroProvider
to grab the correct content and images from the server / CMS / CDN.
How do I "fix"* it so that StaticHero
, way down as a child of Layout
but not Page
is pre-rendered?
I'm hoping there's some "normal" way to do this, and I'm just not thinking through the normal client/server component interleaving options correctly.
* "Fix". I mean, the whole thing works now, but the StaticHero
ends up being the LCP in Lighthouse even if it's just a single line of text; and it's not just a random benchmark, there's often a visible flash as it loads.
Ideas
I have some thoughts on how to do this, but none of them seem easy, so I thought I'd ask here before I engage in "hope coding".
First, I could just move StaticHero
entirely out of the Hero
container, and put it as a child of page
. The big problem with this is that if StaticHero
and HeroThree
don't share the same immediate parent, then things like alignment, size, responsiveness, etc..., all get harder, and feel like they could become very fragile and subject to accidental breaking.
Second, maybe there's some way to use server actions instead of usePathname
and context
to get the content for StaticHero
?
Third, maybe I can do something with parallel routes and handle the animation part somewhere entirely different?
If it seems like any of those, or something entirely different, can be used to solve my problem and you have a pointer to an example doing something similar, I'm happy to take it from there!