Back to News & Insights
Web Development August 18, 2026 · 14 min read

I Was About to Optimize Five Canvases. Two of Them Weren't Running.

This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry. Existing...

I Was About to Optimize Five Canvases. Two of Them Weren't Running.

This is a submission for DEV's Summer Bug Smash: Clear the Lineup powered by Sentry.

Existing codebase: primaworkflows.com, one 321,920-byte HTML file, SHA-256 06a768bb…. All index.html line numbers below are against that published file. prima-visual.js line numbers are against the file the live site serves today (8,773 bytes, SHA-256 dc78c46d…). Every number is measured, and the places where I got one wrong are left in. Production has not been swapped for the after. A same-session A-vs-C table is still empty on purpose.

My site lags on my phone. It is the only page I own that strangers and clients actually load, and it feels slow in the hand. That was the bug. I had the fix written before I opened the file: five stacked canvases, a pile of animation loops, collapse them into one clock and move on.

Two of the four persistent animation loops I was about to collapse never start. The flag gates the call that kicks them off, at :7103.

It gates only that. I want to be exact, because my first draft of this paragraph said the flag also gated their 2D contexts, and then I checked the published file instead of my working copy:

So production still creates three 2D contexts for canvases nothing ever draws to. The loops are dead; the memory is not. Guarding those three lines is a separate one-line-each change that is in my working tree and has not shipped, and I am not counting it in anything below.

Mobile is the measurement that matters here — it is a phone-facing page. So on the device I was optimizing for, the page had one persistent JS loop, not four. The optimization I had specified had almost nothing to collapse. If I had built it and measured, I would have seen nothing move and had no way to tell a failed fix from a fix with no room to work.

I had written the plan from a document I wrote myself, about a file I wrote myself.

Five guards under an unconditional return. Not one of them executes in the published build I audited, and that build is the one live right now. I have not audited every prior deployment, so I am not claiming a start date for it. The patch is deleting that one return. I am not writing the fix in the past tense until the line is gone from the file a visitor loads.

| Guard | Intended | Actual, today | |---|---|---| | prefers-reduced-motion | no WebGL hero | ignored | | max-width: 820px | skip small screens | ignored | | hardwareConcurrency console.warn(...)) where nobody sees it. It pays full price for a feature it cannot run.

Exact diffs of both changed files: gist.github.com/keniel13-ui/3c05e11202d048ad78a11ce2de215e8d — two unified diffs against the deployed 06a768bb file, plus a README of what each hunk does and what is deliberately excluded from the claims. Production has not been swapped, so the before in that diff is what you get if you load the site right now.

The first tag has no defer. The two GSAP tags do. So Supabase is a plain script in : the browser stops building the DOM, fetches it, parses it, executes it, and only then continues. Parser-blocking, in the head, for a library whose every call site is behind a flag that is off.

I had a defer on that line in an earlier version of this post. I put it there. It is not in the file — I had copied the tag out of my own working branch, where a previous change had added one, and pasted it as though it were production. If you are going to quote code, quote the deployed bytes.

Measured from the URLs the production page resolved during this audit. GSAP is pinned to 3.12.5. @supabase/supabase-js@2 is not a pin — jsDelivr treats @2 as a moving major-version alias, and it resolved to 2.112.3 at the time of measurement (x-jsd-version: 2.112.3). If you re-run this later and get a different Supabase number, that is why:

| | gzip transfer | uncompressed JS source | |---|---|---| | supabase-js@2 | 54,607 B | 212,199 B | | gsap.min.js | 28,200 B | 72,214 B | | ScrollTrigger.min.js | 17,681 B | 43,380 B | | total | 100,488 B | 327,793 B |

Those are three different units and I want to keep them apart. 327,793 B is uncompressed JavaScript source — slightly more source than the 321,920-byte HTML document itself. 100,488 B is the measured gzip transfer at the moment of the audit. I re-fetched the same three URLs while finishing this post and got 100,148 B — the uncompressed source did not move, the compressed transfer did. That is the moving @2 alias and CDN recompression showing up in my own numbers, which is exactly why the alias is disclosed above rather than presented as a pin. Both of these are cold-load figures; a repeat visitor with a warm cache does not retransmit them.

The honest headline is the source figure: three libraries carrying more JavaScript source than the entire document they were decorating.

Supabase is straightforward. Every one of its call sites — loadWorldState, loadVisitorAvatars, subscribeToVisitorAvatars — is invoked only inside if (WORLDCANVASENABLED). That flag is false. In the published build audited here those call sites are unreachable — I have not audited every prior deployment, so I am not claiming they never fired in the site's history. The library was still being fetched and parsed on every cold load regardless.

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation