Web performance: fast to load, smooth to play
Fast or forgotten
Remy shares a Hyper Shards link in our Discord. A player taps it on the bus, on a cheap phone with a weak signal. Eight seconds of black screen later, they close the tab. They never even saw the title screen.
Players don't wait. web.dev reports that the BBC lost an extra 10% of users for every extra second its site took to load. Web performance is how fast a page loads and how smoothly it responds. For a game it has two halves: fast to load, and smooth to play for a whole hour.
Bolt says: Smooth frames have their own lesson, 60 frames per second, in Stage 3. This one is about loading, the web's speed scores and long play sessions.
A download budget
- CodeA build tool minifies it: strips spaces and shortens names. Then we check which libraries we really use, because one big library can weigh more than the whole game.
- ImagesSprite sheets at the right size for the screen, in compact formats. The asset pipeline lesson in Stage 3 shows how art gets packed and shrunk.
- AudioOften the heaviest part. Short, compressed sound effects, and music that arrives after the game has started.
Every file must travel over the player's connection before the game can start. A performance budget is a limit the team agrees on, like pocket money: "the first load stays under 3 MB". If a change breaks the budget, something else has to shrink.
Bolt says: A robot can guard the budget: a CI check that fails the pull request when the game's files grow past the limit.
Core Web Vitals: three numbers
- LCP: Largest Contentful PaintHow long until the biggest image or block of text on screen appears. Good: 2.5 seconds or less. Poor: over 4 seconds.
- INP: Interaction to Next PaintFrom a click, tap or key press until the screen next updates. Good: 200 milliseconds or less. It became a Core Web Vital in 2024.
- CLS: Cumulative Layout ShiftHow much things jump around unexpectedly, like a late banner shoving the Start button down just as you tap. Good: 0.1 or less.
Google grades websites with three Core Web Vitals. A page counts as good when at least 75% of visits hit each target (the 75th percentile), measured separately on phones and computers.
Read the numbers
1play.ourstudio.com, real players, last 28 days2(75th percentile)3 4LCP 3.9 s5INP 140 ms6CLS 0.02
Which vital misses "good", and which fix fits it?
- LCP: the title screen waits for big files, so show the logo and Start button first
- INP: taps are slow, so make every button bigger
- CLS: the page jumps, so remove the logo
Show the answer
LCP: the title screen waits for big files, so show the logo and Start button first
Good LCP is 2.5 seconds or less, so 3.9 needs improvement (poor starts above 4). INP at 140 ms is under 200, and CLS at 0.02 is under 0.1, so both are already good.
Lighthouse and PageSpeed Insights
- LighthouseBuilt into Chrome DevTools. It loads the page as a simulated mid-range phone on a mobile connection, then scores performance from 0 to 100 (90 or more is good, under 50 is poor) and lists what to fix.
- PageSpeed InsightsAt pagespeed.web.dev. It runs Lighthouse on any web address, and once enough people visit, it adds what real Chrome users got over the last 28 days.
- Lab vs fieldLab data is one test run, the same every time, so it is great for checking a fix. Field data is what real players actually got, on their own phones and networks. When they disagree, the field wins.
Load only what's needed first
Lazy loading means fetching a file only when it is needed. A player on World 1 doesn't need World 5's boss yet.
- Code splitting cuts the JavaScript into chunks.
await import('./world5.js')fetches that chunk when World 5 starts. - PixiJS groups art and sound into bundles and can load later ones in the background while you play.
- On web pages,
<img loading="lazy">waits until an image is about to scroll into view.
A canvas never counts for LCP, so the HTML title logo usually does. Show it first.
Bolt says: Second visits should cost almost nothing. Cloudflare keeps copies near players (the Cloudflare lesson), the browser gets a tiny "304 Not Modified" for files that haven't changed, and our service worker serves the rest from the device.
Load order
1await Assets.load('title-logo.png');2showTitleScreen();3await Assets.load('world1.json');4await Assets.load('music-all-worlds.mp3'); // 18 MB5enableStartButton();
Players stare at a grey Start button for ages. Which line should change?
- Line 1
- Line 3
- Line 4
Show the answer
Line 4
Line 4 makes Start wait for 18 MB of music for every world. World 1 only needs its own track, and the rest can load in the background once Start works. Lines 1 and 3 load what the first screen and first world really need.
Memory leaks in long sessions
The browser's garbage collector frees memory that nothing points to any more. A memory leak is memory something still points to, but the game never uses again. After an hour of waves, the game stutters, then the phone closes the tab.
- Objects left in a list, like every boss ever spawned.
- Event listeners added each wave and never removed. Each keeps alive everything it mentions.
- Textures, pictures held by the GPU. PixiJS frees them at once only when we call
destroy().
In DevTools, the Memory panel takes heap snapshots: play 10 waves, snapshot, play 10 more, snapshot, and compare what grew.
Bug hunt
1function startBossWave() {2 const boss = new Sprite(bossTexture);3 stage.addChild(boss);4 window.addEventListener('resize',5 () => fitToScreen(boss));6 return boss;7}8function endBossWave(boss) {9 stage.removeChild(boss);10}
Line 9 takes the boss off the stage, so the garbage collector can now clean up every old boss.
- True
- False
Show the answer
False
Lines 4 and 5 add a resize listener that still points at the boss, and nothing removes it. Every boss wave leaves one more boss stuck in memory. The fix: remove the listener with removeEventListener and call boss.destroy() when the wave ends.
Test on a cheap phone
Our laptops are far faster than most players' phones. A cheap phone has a slower processor, so the same JavaScript takes longer to start, and less memory, so a leak crashes it sooner.
DevTools can slow down the CPU and network to imitate a phone, and Lighthouse already tests as one. But the honest test is real: keep a cheap Android phone in the studio, play for 30 minutes, and plug it into the laptop with USB so Chrome DevTools can watch it. That is called remote debugging.
Bolt says: Hyper Shards is light and quick now. Next: the phone stores, where Garden Defense will live.
Fact-checked 9 October 2026
- web.dev: Web Vitals web.dev
- web.dev: Largest Contentful Paint web.dev
- web.dev: Interaction to Next Paint web.dev
- web.dev: Cumulative Layout Shift web.dev
- web.dev: why speed matters web.dev
- PageSpeed Insights: about developers.google.com
- MDN: lazy loading developer.mozilla.org
- PixiJS: garbage collection pixijs.com
- PixiJS: assets pixijs.com
- Chrome DevTools: memory problems developer.chrome.com
- Chrome DevTools: remote debugging Android developer.chrome.com