Skip to content

Stop the whole dashboard waiting on one query before anything can fetch - #30

Merged
ThinkingSpade merged 1 commit into
mainfrom
perf/remove-dashboard-waterfall
Jul 31, 2026
Merged

Stop the whole dashboard waiting on one query before anything can fetch#30
ThinkingSpade merged 1 commit into
mainfrom
perf/remove-dashboard-waterfall

Conversation

@ThinkingSpade

Copy link
Copy Markdown
Owner

First fix from a production performance measurement. It removes one of three
waves
, not the per-call cost.

What I measured on prod (2026-07-31, signed in)

what time
/favicon.ico — static asset, Worker never runs 0.07s
/api/does-not-exist404, no handler, no DB, no auth 4.16s
/api/auth/get-session 4.04s
Keywords page, last server function finishes 15.8s

A 404 costs the same as a real request, so ~4s is fixed per-invocation overhead
before any of our code runs. Ten back-to-back calls: 3.4 / 4.0 / 5.7 / 5.1 / 3.9
/ 4.2 / 4.6 / 4.6 / 3.4 / 3.8s — not one fast, so there's no isolate reuse to
wait for.

The page fires 14 server functions in three sequential waves (~1.7s, ~5.7s,
~11.5s). The waves are what multiply the 4s into 16s.

The change

The dashboard put every card behind projectsQuery.isPending. Since that query
costs ~4s, no card could begin fetching until 4s in — turning independent
queries into a second wave.

projectId comes from the route params, not from that query. Seven of the nine
cards are keyed only on it, and each already renders its own skeleton and
degrades independently — which is why removing the page-level gate leaves no
holes.

The two that genuinely need domain (AnalyzeProjectCard, BacklinksCard)
still wait, rendered in place behind a skeleton so the layout doesn't reflow.

DashboardLoadingState goes with it — the header now has its own skeleton and
every card brings its own, so it had no caller left. knip caught that.

What this does not do

It does not fix the 4s. And the obvious fix for that was already tested and
falsified on 2026-07-29: cutting the startup chunk 30% (5,933 → 4,166 KB)
moved it not at all. It isn't parse time — the remaining suspect is top-level
module evaluation (agents, think, ai, better-auth, autumn all
initialise at import). Please don't attack it with more bundle shrinking.

On Supabase, since it prompted this

It would fix none of this. The session table has 2 rows with a unique
index on token; a 404 touches no database and costs the same 4s. An
independent Codex pass agreed and added a detail I'd missed: the existing
Postgres path uses Hyperdrive with max: 1, so Promise.all sections would
queue on one connection — potentially making parallel work slower, plus
regional latency.

Verification

pnpm ci:check clean, 2,129 tests passing.

The wall-clock win is not yet proven — it needs a production re-measure after
deploy, using exactly the numbers above. That's the next step, not an assumption.

🤖 Generated with Claude Code

Measured on production 2026-07-31: the project keywords page fired 14
server functions in THREE sequential waves (starting ~1.7s, ~5.7s, ~11.5s)
and finished at 15.8s.

The waves are what cost, because a server-function call against this Worker
carries ~4s of FIXED per-invocation overhead. That figure is not
speculation: `/api/does-not-exist` -- a 404 that runs no handler, touches no
database and does no auth -- takes 4.16s, while a static asset on the same
host takes 0.07s. Ten back-to-back calls were 3.4-5.7s with not one fast, so
there is no isolate reuse to wait for.

The dashboard put every card behind `projectsQuery.isPending`. Since that
query costs ~4s, no card could BEGIN fetching until 4s in, turning
independent queries into a second wave.

`projectId` comes from the route params, not from that query. Seven of the
nine cards are keyed only on it, and each already renders its own skeleton
and degrades independently -- which is why removing the page-level gate
leaves no holes. They now start immediately.

The two that genuinely need the project's `domain` (AnalyzeProjectCard,
BacklinksCard) still wait, and are rendered in place behind a skeleton so
the layout does not reflow when they arrive.

`DashboardLoadingState` goes with it: the header now carries its own
two-line skeleton and every card brings its own, so the page-level loader
had no remaining caller. knip caught it.

This does NOT fix the 4s. It removes one of the three waves that multiply
it. The 4s itself was investigated on 2026-07-29 and the obvious fix was
FALSIFIED -- cutting the startup chunk 30% (5,933 -> 4,166 KB) moved it not
at all, so it is not parse time. Do not attack it with more bundle
shrinking.

ci:check clean, 2,129 tests passing. The wall-clock claim needs a production
re-measure after deploy; that is the next step, not an assumption.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
flyrocketseo c72bdc0 Jul 31 2026, 07:16 AM

@ThinkingSpade
ThinkingSpade merged commit f7b69cf into main Jul 31, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant