oscklm
oscklm17mo ago

Preloading data in Server Components Next.js

I don't seem to find anything in docs in regards to preloading data? I might be completely misunderstanding something, since coming from React Query. But is there a way to preload some data on the server, and then passing that to the local store, so there is data already there when the user first loads a page?
14 Replies
ballingt
ballingt17mo ago
We've prototyped integrated approaches like this before but we currently recommend passing the data through and then defaulting to it manually at the useQuery() call site with const data = useQuery(api.foo.bar) || preloadedData
ian
ian17mo ago
And the way to fetch data on the web server would be to use a Node.js client to fetch it from convex https://docs.convex.dev/client/javascript/node and especially https://docs.convex.dev/client/react/nextjs/#server-side-rendering are good resources here
Node.js | Convex Developer Hub
Convex supports point-in-time queries (see
Next.js | Convex Developer Hub
How Convex works in a Next.js app
Michal Srb
Michal Srb17mo ago
(Actually it's the ConvexHTTPClient, which is not Node.js specific, see https://docs.convex.dev/client/javascript#http-client )
JavaScript | Convex Developer Hub
Convex applications can be accessed from Node.js or any JavaScript runtime that
oscklm
oscklmOP17mo ago
Awesome thanks to both of you. Just curious, in terms of a nice UX when the data is loading? is there a recommended way of doing this from you guys It's a minor thing, but i see a slight flash of content when loading moving between pages, when the cache is being loaded. I don't find it to be fixable with a loading indicator as that would be flashing in out quickly too. Yeah thanks. I did play with the ConvexHTTPClient in my server component that worked perfectly fine. So i guess passing that down with the approach tom mentioned would work, im gonna test that out.
Michal Srb
Michal Srb17mo ago
I'd say the simplest approach is to keep things clientside (no preloading), but use a nice fade-in animation for the loading. You can do thinks like not show a skeleton if the loading takes more than x ms. The challenge with preloading is that without an integrated approach you have to handle each query twice (on client with useQuery and on server with HTTPClient), which can be cumbersome - but if you can load some small simple data to get the page loading, it can work well. Really depends on your app.
oscklm
oscklmOP17mo ago
That makes sense, was thinking about some sort of skeleton UI even though i haven't tried this approach before in my previous apps. Thanks for clearing this up for me Michael. I agree, i also see the whole preloading thing bringing issues with it. So yeah it might fix the whole having data visible to the user straight away, but the approach u mention def seems like a good balance of easy to do and still nice experience for the user
ian
ian17mo ago
A bit advanced, but You can preload the query or keep it around between pages by using the convex.wathQuery function to make a Watch that you can use to hold a long-standing subscription open with onUpdate which returns a function to unsubscribe when you're no longer interested. You could theoretically pre-warm queries and keep them active for certain pages of your client-routed app.
ballingt
ballingt17mo ago
this ⬆️ is pretty slick, we've seen folks do this on button/link hover: if you know what queries a view will need and the Convex Provider (or the ConvexReactClient it provides) persists between two views, you can
function onHover() {
const unsub = client
.watchQuery(api.foo.bar, {})
.onUpdate(()=>{});
setTimeout(unsub, 5000);
}
function onHover() {
const unsub = client
.watchQuery(api.foo.bar, {})
.onUpdate(()=>{});
setTimeout(unsub, 5000);
}
oscklm
oscklmOP17mo ago
Awesome stuff, really cool approach with pre-warming the queries on button hover. I can see this working great 🤩
winsoroaks
winsoroaks17mo ago
sorry to hijack this thread. i think it's relevant to what i have in mind
const { userId, isLoaded } = useAuth()
const access = useQuery(api.employeeFuncs.isUserAllowed, {
userId: userId || "",
pageName: "team",
})
const { userId, isLoaded } = useAuth()
const access = useQuery(api.employeeFuncs.isUserAllowed, {
userId: userId || "",
pageName: "team",
})
page.tsx:24 🚀 access: undefined
page.tsx:24 🚀 access: undefined
page.tsx:24 🚀 access: undefined
page.tsx:24 🚀 access: false
page.tsx:24 🚀 access: true
page.tsx:24 🚀 access: undefined
page.tsx:24 🚀 access: undefined
page.tsx:24 🚀 access: undefined
page.tsx:24 🚀 access: false
page.tsx:24 🚀 access: true
say i wanted to do the above in my client side page, because userId takes a while to load, the access value im getting goes from undefined -> false -> true. i think it'd be better for me to do server side for this part. what do y'all think?
ballingt
ballingt17mo ago
What are your goals or your concerns with this approach? Yeah combining multiple queries into one is often a good approach. Check out "skip" in the useQuery() docs to avoid passing an empty string fake userId if you want to do avoid that
winsoroaks
winsoroaks17mo ago
thanks so much tom. it flashes a sec for the "falsy" component then shows the true component
ballingt
ballingt17mo ago
You probably want all three values, undefined and false and true: undefined means loading, false means this user is not allowed (show the NOT ALLOWED UI), true means allowed got it, yeah skip should help with making that false undefined i stead
winsoroaks
winsoroaks17mo ago
damn it worked hello @convex_CEO, pls give tom a raise 🙂 ugh i should have studied the doc 😛

Did you find this page helpful?