Your vibe-coded app was fast when you built it. Then real people started using it, and it began to freeze: logins hang, buttons lag, a pop-up takes a second to open.
That happened to NudgeCue, my free classroom app, once it passed 150 teachers. In one day we found five separate causes and fixed all of them. None of them showed up in testing, because testing used one person, a fast laptop and a tiny amount of data.
This post walks through each one: what it was, how we measured it, and the fix. If your app is built with an AI coding tool and a hosted database like Supabase, there's a good chance at least one applies to you.
Before you fix anything: measure
The temptation is to ask your AI tool "why is my app slow?" and accept the first answer. Don't. You'll get a plausible guess, and plausible guesses are how you end up rewriting the wrong thing.
Three ways to get real numbers:
- Your database's own advisor. Supabase has performance and security advisors in the dashboard. They point at real problems in your setup, with links to the fix.
- Your server logs. How long each request takes, from where, and how many fail. Supabase keeps these too.
- Your browser on a slow computer. Chrome DevTools can slow your CPU down 4x (Performance tab, CPU throttling). Your users are not on your laptop. Ours are on school PCs.
Everything below came from one of those three.
1. Images that were far too big
What we found: the student avatars were the biggest thing the app downloaded. Many were 1024-pixel PNGs, around 300 KB on average and up to 1.6 MB each, shown at about 100 pixels on screen. Every class screen loads a whole class of them. Our hosting plan's data allowance was running low.
The fix: we resized all 447 avatars to at most 512 pixels (still twice the size they're shown at) and saved them with a 256-colour palette. Total size went from 136 MB to 13 MB. At the size they're shown, you can't tell the difference.
The detail that mattered: we kept the same file names. Every student's avatar is stored in the database as a file path, so renaming the files to a "better" format would have broken all of them. Same names, smaller files, nothing in the database had to change.
Check yours: sort the images in your public folder by size. Anything over 200 KB that's shown small is a candidate.
2. A decoration that kept the computer busy
What we found: the class screen had Halloween decorations, including two pumpkins with a glowing effect. With nobody touching anything, the screen kept a slowed-down CPU about 31% busy. Turning the decorations off dropped it to zero. Turning off just the pumpkin glow dropped it to 4%.
Why: the glow animated a CSS drop-shadow filter. The browser can't hand that to the graphics card, so it redraws it on every frame, all lesson long. On a slow school PC, that's enough to make every pop-up stutter.
The fix: we moved the glow to its own layer and animated only opacity and transform, which the graphics card handles. Same look. CPU went from 31% to 6%.
Check yours: any always-on animation that changes filter, box-shadow, width, height or colours is suspect. Animate opacity and transform instead.
3. Security rules that re-checked every row
This one is the most "vibe coding" problem on the list, because the AI wrote it correctly and it still caused trouble.
What we found: the points table had 43,000 rows and grows by about 9,000 a week. Its security rules (Supabase calls them row level security policies) said "a teacher can only see points for students in their own classes". Correct, but written so the database worked out who the signed-in teacher was again for every single row it checked. 64 rules across the app were written the same way.
The fix: Supabase's documented one: wrap auth.uid() in a select, so the database asks once per query instead of once per row.
-- before
using (teacher_id = auth.uid())
-- after
using (teacher_id = (select auth.uid()))
We also added indexes for the lookups that scanned the whole table, like "points given today" (by date) and "homework points" (by category).
How we made sure it broke nothing: security rules are the scariest thing to touch in a live app. Before changing anything, we saved a copy of every rule as an undo script. Then we counted, for four kinds of user (a big account, a small one, an admin and a signed-out visitor), how many rows each could see in every table. After the change we ran the same counts. Every number matched. We also checked that a teacher could still add points to their own students and not to anyone else's.
Check yours: Supabase's performance advisor flags this as "Auth RLS Initialization Plan". If you see it, the fix is the one above.
4. Signing in loaded everything twice
What we found: Google sign-in felt frozen for a moment, and the app seemed to reload whenever a teacher switched back to its tab.
Why: the app refetched all its data every time the auth library said "signed in". But Supabase sends that event when a session is restored and when a tab regains focus, not just when someone actually signs in. So every return to the tab reloaded the whole class.
The fix: remember whose data is loaded, and only refetch when a different person signs in.
Check yours: search your code for onAuthStateChange. If it refetches or invalidates everything on SIGNED_IN, it's doing this too.
5. Distance you can't fix, but can plan for
What we found: the server logs showed simple requests taking 250 to 450 milliseconds, while the database answered them in about 2. The rest was distance: the database is in Singapore, and the teachers are spread across the Middle East, the US and Asia.
What to do: you can't make light faster, so make fewer trips. Don't load data you already have (that's fix 4), load things in parallel instead of one after another, and show something useful while you wait. A spinner after a click feels slow. An instant change that saves in the background feels fast.
A prompt to start with
If you want your AI tool to look for these, give it the job of measuring first:
My app gets slow with real users. Before changing any code:
1. List every image over 200 KB in the public folder and where it's shown.
2. List always-on CSS animations that change filter, box-shadow or size.
3. Find code that refetches all data on auth state changes.
4. If I paste the Supabase performance advisor results, explain each one.
Report what you find with file names. Don't fix anything yet.
Then fix one thing at a time, and measure again after each one.
What I'd tell myself a month ago
Your app being slow with real users isn't a sign you built it badly. It's a sign people are using it. The work is the same as with any app: measure, find the one thing that's actually expensive, fix that, measure again.
If you're still at the "it works but looks AI-made" stage, start with how to make your vibe-coded app not look vibe-coded. For the whole path from idea to live app, there's a free vibe coding starter pack.
References
- Supabase: row level security, call functions with select - Why wrapping
auth.uid()in a select makes policies faster. - Supabase database linter: Auth RLS Initialization Plan - The advisor warning that flags this problem.
- Chrome DevTools: Performance panel - Recording performance with CPU throttling to see what a slow computer sees.