#Proper way of prefetching & skeleton loaders in Tanstack Start

35 messages · Page 1 of 1 (latest)

summer frigate
#

What is the proper way of prefetching data and also loading skeletons until the data is available in Tanstack Start?

Currently, I'm running a prefetchQuery in the route loader (not awaited), then in the component using useQuery with isLoading to show skeleton loaders.

The docs seems to also use this approach is some cases. However, I seem to be getting a Hydration issue. If I reload the page on routes that use this, a hydration error occurs. Is this a bug or is there a different approach I should be using to prefetch data and show loading states?

halcyon loom
#

useQuery does not run on the server

#

use useSuspenseQuery instead

summer frigate
halcyon loom
#

when would you use useQuery
for things that are not SSRed

summer frigate
# halcyon loom no need to wrap them in suspense if you have set up a pending component in the r...

Ok, I think I might need this explained in simpler terms. In my scenario I have a posts page with an infinite scroll. Ideally we prefetch on hover the first page of posts on link hover. That posts route contains quite a few other components like a header bar, filters, etc... So I don't really want the entire route to be a pendingComponent while it waits for data. I just want the posts to show skeleton loaders while they are being fetched.

halcyon loom
#

yes then sure, you need a more fine grained suspense boundary

summer frigate
# halcyon loom yes then sure, you need a more fine grained suspense boundary

Ok, so the 2 options for SSR'd queries are either pendingComponent for route level calls, or more fine grained components using Suspense + useSuspenseQuery. For things that are dynamically loaded (not prefetched), we can use useQuery with isLoading?

And I shouldn't ever use useQuery with prefetched content as it can cause an SSR mismatch?

Is this right, or am I missing something?

halcyon loom
#

you can use useQuery e.g inside ClientOnly or with ssr:false or ssr:"data-only"

summer frigate
#

Right, but obviously you then lose out on SSR benefits...

Ok, I think I understand now, thanks 👍

summer frigate
#

Actually, just had a thought. If useQuery doesn't work on the server and the reason I'm getting hydration errors is due to prefetching the queries and using useQuery(), can't I just not prefetch on SSR?

Something like: if (typeof document !== "undefined") {...} in the loader?

halcyon loom
#

i think the hydration error is not originating from the prefetching

#

why not just use useSuspenseQuery ?

summer frigate
# halcyon loom i think the hydration error is not originating from the prefetching

I have a simple route that has a prefetchQuery() in the loader, and a useQuery() in the componet. If I use the data in the component, it throws a hydration error. If I remove the prefetch from the loader, it doesn't 🤷‍♂️

As for not using useSuspenseQuery(), I'm having issues switching from skeleton loader to actual component. Without and overriden slowdown, I don't see the skeleton loaders at all (which is expected because the request is fast), however I don't see the cards either for around 500ms, then they pop in. If I put a manual 2s slowdown on the route, I see the skeleton loaders, then when they load, depending on if there's a lot of posts or not, it either instantly switches to the proper cards, or I see blank page for around 500ms before they show up.

I'm using Tanstack Virtual to render the list of items, which might be why

#

I'll probably use useSuspenseQuery() where possible, as it seems to most straightforward, but the DX for useQuery I still prefer (being able to use isLoading/isPending, etc), so that's why I might do the if (typeof document !== "undefined") {...} approach elsewhere

halcyon loom
#

probably best if you provide a complete minimal reproducer

summer frigate
#

The routes /creators and /creators-no-delay

The /creators route has a manual 2s delay, and you can see a slight flicker between rendering the skeletons -> blank -> creator cards

The /creators-no-delay doesn't even show the skeleton loaders, and instead is just blank -> creator cards

I presume this has something to do with Tanstack Virtual as the /creators-no-virtual seems to have no delay switching from skeleton to creator cards

halcyon loom
#

in the /creators-no-delay route, there is no suspense happening as the query function is synchronouos

#

hence no fallback is rendered and virtualized list is not rendered on the server

#

on the /creators route, suspense kicks in and the fallback is rendered

summer frigate
#

Claude did me dirty there, 1 sec

#

Ok, I just moved all the awaits to the generateCreators() function, and also added a 10ms delay to the no-delay route, and it still produces the same outcome

Reloading /creators route flashes from skeleton to creator cards
Reloading /creators-no-delay shows nothing for 100ms or so before showing the creator cards (no skeleton)

#

Pushed to repo

halcyon loom
#

so there is still no suspense in the /creators-no-delay route as the loader runs before react renders, and although you dont await it, it completes before react renders this component and the data is available without suspending

summer frigate
#

So the query responds faster than react can render, so that blank 100ms between page load and creator cards showing is just react rendering?

Ok, but then how can /creators-no-virtual not have a flash, it's seamless from skeleton to card. Is that due to both the server and client rendering the same thing, whereas since react virtual requires the DOM, there's always going to be some sort of rendering step?

halcyon loom
#

look at the query function . it has a delay

summer frigate
#

Even without the delay

#

Reloading the /creators-no-virtual route with no delay instantly shows the creator cards (no flash), and adding a delay seamlessly goes from skeletons to creator cards

halcyon loom
#

virtual list cannot be rendered on the server since it cannot measure

summer frigate
#

Yep, and it's wrapped in a suspense, and the skeletons loaders don't use virtual

#

But the transition from skeleton card to creator card which are virtualised, seems to go skeletons -> blank page for anywhere form 10ms to 100ms -> creator cards showing

#

The /creators-no-virtual route doesn't do this, there is no blank page flash between skeletons to cards. It's not the end of the world if there is a flash, but ideally I'd like it to be perfectly seamless between skeleton loader and creator card for virtualised lists as well

halcyon loom
#

probably best to ask this in #1023929636855496754