ARTICLE AD BOX
your approach is basically correct. 🙂
Fetch once at the top, hydrate a global store, let all cards subscribe to it.
I’d just tweak how you fetch, not the whole idea.
In the App Router you don’t need a server action for the initial read. your layout.tsx is already a server component, so you can
import { FavoritesHydrator } from '@/components/favorites-hydrator' import { getUserFavorites } from '@/lib/favorites' export default async function RootLayout({ children }) { const favorites = await getUserFavorites() // DB/API on the server return ( <html> <body> <FavoritesHydrator initialFavorites={favorites}> {children} </FavoritesHydrator> </body> </html> ) }then
'use client' import { useEffect } from 'react' import { useFavoritesStore } from '@/stores/favorites' export function FavoritesHydrator({ initialFavorites, children }) { const setFavorites = useFavoritesStore(s => s.setFavorites) useEffect(() => { setFavorites(initialFavorites) }, [initialFavorites, setFavorites]) return children }And cards just do:
const isFav = useFavoritesStore(s => s.ids.includes(storeId))
Now every place that renders that store will stay in sync automatically.
Is fetching in the layout “bad” / blocking?
Not really. Server render has to wait for user favourites somewhere anyway. Doing it in the root layout means:
one server fetch instead of many client fetches,
no extra round-trip,
less chance of “flash” while favourites load.
