I’m working on a client portal project hosted on igotucorp.com and running into a bit of a performance snag with some Velo backend queries.
We have a dynamic page that pulls user activity logs and custom metrics from a Data Collection containing several thousand rows. During peak traffic hours, the frontend data loading causes a noticeable delay before the repeater populates.
Here is a simplified snippet of what we’re running in our backend module (dashboard.jsw):
Good setup already with the indexes and the limit(50). A few things that helped us on similar client-portal dashboards:
Caching: for data that does not change every request (user metadata, config, anything mostly static per session), wix-storage session storage on the client is usually enough and skips a backend round trip entirely. If you want server-side caching across users, there is a caching helper in wix-data-backend (and the newer backend caching utilities) you can wrap fetchUserMetrics in with a short TTL, 30-60s is plenty for a dashboard that is not real-time. That alone usually kills most of the “redundant backend calls” problem without adding real staleness.
Aggregation: put it in a backend web module, not client-side hooks. Dataset/page hooks run per visitor on every load, so any aggregation there repeats the work each time and ships more JS to the client. A .jsw function keeps the heavy lifting server-side, lets you control exactly what gets returned, and is the only place you can safely do multi-collection joins or math across the full dataset before sending 50 rows down.
One more thing worth checking since you are already a few thousand rows in: skip()-based pagination degrades the deeper you go. If anything downstream of this paginates past page 1, switch to cursor-based paging (the result object’s .next() from find()) instead of bumping skip() - it stays fast regardless of how deep the user scrolls. And if “custom metrics” means anything computed (counts, sums, rollups), consider precomputing those into a small separate collection on a scheduled job rather than aggregating at request time - much cheaper than recalculating on every page load during peak hours.
Two things in the snippet first. wix-data-backend isn’t a module, the import should be import wixData from 'wix-data';. And with the indexes you already added, the query itself is fine for a few thousand rows, so the delay is more likely in how the page calls it.
Return only what the repeater needs: add .fields("title", "value", "_createdDate") (your own field names) so the payload stays small.
For caching, skip wix-window. On the page, memory from wix-storage-frontend works for per-visit data you don’t want to refetch, and session survives a refresh. In the backend, don’t rely on module-level variables, they can reset between calls.
Heavy aggregation belongs in the backend. Use wixData.aggregate() with .group() and .sum() inside your .jsw rather than pulling rows to the client and summing there. If the metrics are mostly static, precompute them: a scheduled job (jobs.config) can write a daily summary row per user to a small collection, so the page reads one item instead of aggregating on every load.
Keep data hooks light, since a beforeQuery or afterQuery hook runs on every read. On the page, start the fetch in onReady without blocking the layout and show a loader until it resolves.
Check the Site Monitoring logs during peak hours to see whether the time is going to the query or to the round trip.