“Instant play” is one of those phrases that sounds like marketing until you have to engineer it. On the Suntongames platform, the promise we make to every visitor is concrete: pick a puzzle, and the game is interactable on a 4G mobile connection in under three seconds. Not a loading splash that pretends to be fast — actually playable, with input registered, audio ready, and the first level rendered.
This article is the engineering playbook behind that number. Nothing here is exotic. The hard part was not inventing new techniques; it was refusing to ship until each of the boring ones was done correctly and in the right order.
The Budget: Why Three Seconds, And Three Seconds of What
Before optimizing, we had to define the metric. We picked Time to First Meaningful Interaction (TTFMI): the elapsed wall-clock time from a tap on a game tile to the first frame in which the player can produce a meaningful game input and see a meaningful response. Crucially, TTFMI is measured on a throttled 4G profile (9 Mbps down, 170 ms RTT) on a mid-tier Android device, not on a developer laptop on fiber. If a feature ships and the median TTFMI on that profile crosses 3.0 seconds, the feature is reverted.
Three seconds is not a magic number from a benchmark. It is the threshold at which, in our internal playtesting, the drop-off curve starts to bend sharply upward. Below three seconds, players stay. Above it, a measurable slice simply leaves before the first move. This lines up with Google's own guidance on Core Web Vitals, which frames sub-three-second load as the baseline for a usable mobile experience rather than an aspirational target.
A fast loading bar is a lie. The only honest measure of “instant” is whether the player’s first tap on a game element actually does something.
Asset Strategy: WebP Banners and Aggressive Right-Sizing
The biggest single lever was, predictably, image weight. Puzzle game catalog pages are image-heavy by nature — every game needs a banner, a thumbnail, and a preview frame. Our original pipeline served PNG/JPG banners at 600–900 KB each, which on the throttled 4G profile alone consumed more than half our time budget before any JavaScript had run.
The fix happened in three stages.
- Format conversion to WebP. Every banner and preview image is now generated as WebP at build time. For the photographic and gradient-heavy artwork that dominates our catalog, WebP at quality 78 is visually indistinguishable from the source PNG and roughly 35–45% smaller. Where a browser lacks WebP support, we fall back to AVIF-less JPG through the
<picture>element, but in practice the unsupported slice is now under 2% of sessions. - Per-slot sizing, not source sizing. A banner displayed at 480×270 is generated at 480×270 (and a 2× variant for retina). We stopped shipping a single 1200px image and letting CSS shrink it. This alone removed roughly 600 KB from a typical landing view.
- Lossless for UI, lossy for artwork. Buttons, logos, and any element with hard edges are served as lossless WebP. Photographic banners are lossy. Mixing the two based on content type rather than format-only saved another 10–15% with zero visible regression.
Code Splitting: One Chunk Per Game, Not One Bundle Per Site
The second-biggest win was structural. The naive way to build a multi-game portal is a single JavaScript bundle that knows about every game and decides at runtime which one to mount. That bundle grows linearly with your catalog. By the time you have 40 games, the bundle is multiple megabytes, and the browser has to parse all of it before it can mount anything.
Our architecture does the opposite. Each game is its own async chunk, named and route-mapped. The shell bundle — the navigation, the catalog grid, the common UI — is around 60 KB gzipped and is the only JavaScript that loads on a fresh landing. When a player taps a game, the browser fetches only that game’s chunk and its direct dependencies.
// simplified route map
const routes = {
"color-sort": () => import("./games/color-sort.js"),
"line-connect": () => import("./games/line-connect.js"),
"bubble-shoot": () => import("./games/bubble-shoot.js"),
// ...
};
The dynamic import() produces a separate HTTP request, but on HTTP/2 multiplexing the additional request is cheap, and because the chunk is small and cacheable, the marginal cost on repeat visits is effectively zero. The result: the shell parses and mounts in well under a second, the catalog becomes interactive immediately, and the player’s chosen game chunk streams in while they are reading the title.
Lazy-Loaded Images: Below-the-Fold Is Not Free
Even with WebP and right-sizing, a catalog grid with 40 thumbnails is still a lot of bytes if loaded eagerly. We use native lazy loading for every image that is not above the fold, plus loading="eager" with fetchpriority="high" for the first few visible tiles. There is no Intersection Observer polyfill in our bundle — native browser support is now universal enough across our audience that the polyfill was net-negative weight.
One subtle but important detail: the Largest Contentful Paint (LCP) element on a game page is almost always the banner of the selected game, not the page chrome. We therefore preload that banner with <link rel="preload" as="image" fetchpriority="high"> in the document head. Without that preload, the browser discovers the banner only after parsing the shell HTML and chunk manifest, which costs roughly 200–300 ms of avoidable wait.
Caching: The Second Visit Is the Real Win
The first visit is the hard case, but the typical Suntongames player returns. On the second visit, our target is not three seconds — it is under one second to interactive. That requires caching that is both aggressive and correct.
- HTML is short-cache. The shell HTML is cached at the CDN for a few minutes with stale-while-revalidate. This lets a returning visitor get an instant 304-equivalent while still picking up content updates within minutes.
- Static assets are long-cache, content-addressed. Every JS chunk, image, and font carries a fingerprint in its filename and is served with
Cache-Control: public, max-age=31536000, immutable. When a game updates, its chunk filename changes, invalidating only that one file. - Service worker, optional. We ship a small service worker that precaches the shell and the top games. It is strictly an enhancement — the site is fully functional without it, and we are careful never to make a request depend on the worker being alive.
Core Web Vitals: What We Actually Track
We instrument three Core Web Vitals continuously, not as a one-time audit. The table below is roughly what “healthy” looks like for our pages on field data, not lab data. The thresholds themselves are not our invention — they are the official rating bands published by Google, and they are the same bands the HTTP Archive Web Almanac 2024 uses to benchmark the web at scale.
| Metric | P75 target | What we do if it regresses |
|---|---|---|
| Largest Contentful Paint (LCP) | < 2.0 s | Re-check banner preload & LCP candidate |
| Interaction to Next Paint (INP) | < 180 ms | Profile long tasks on game mount |
| Cumulative Layout Shift (CLS) | < 0.05 | Reserve aspect-ratio boxes for media |
The single most impactful INP fix was not in game code at all — it was reserving fixed aspect-ratio boxes for every banner and tile so the layout never shifts during image load. Layout shift does not just hurt CLS; it hurts the perceived responsiveness of taps that land on the wrong target after a reflow.
Performance Monitoring: Field Data, Not Lab Theatre
Lab tools like Lighthouse are useful for regression checks but they lie to you in a specific way: they run on a fast machine and treat one synthetic visit as representative. Real performance happens on a four-year-old Android phone on a congested cell tower. We collect Real User Metrics (RUM) from a sampled subset of sessions — LCP, INP, CLS, TTFB, and our own TTFMI — and we slice them by device tier, connection class, and geography. This is the same field-data philosophy behind Google's Chrome User Experience Report (CrUX), which powers the Core Web Vitals scores you see in PageSpeed Insights and Search Console.
The HTTP Archive Web Almanac 2024 found that fewer than half of mobile origins pass all three Core Web Vitals at the "Good" threshold — a reminder that the median web is still slow, and that "instant play" is a genuinely competitive bar rather than table stakes.— HTTP Archive Web Almanac 2024, Performance chapter
Two rules govern that collection. First, we sample. We do not need every page view instrumented to know the median; a small, well-distributed sample is enough and respects the user’s bandwidth. Second, we alert on the P75, not the median. A median can stay healthy while the long tail degrades, and the long tail is where churn lives.
What We Did Not Do
Worth listing explicitly: the things we tried and rejected, because “instant play” is also about not shipping complexity that does not earn its weight.
- No SSR framework. Our shell is static HTML. The win from server-rendering a near-static page did not justify the runtime cost and operational surface.
- No heavy animation library. Puzzles do not need spine animation frameworks on the catalog page. Motion is CSS where possible, the game canvas where not.
- No third-party tag soup. Each third-party script is a vector for an INP regression we cannot directly fix. We carry the minimum, and we sandbox what we carry.
Instant play is not a feature. It is a discipline of saying no — to weight, to synchronous work, to cleverness that the player never sees. Three seconds is not the floor we are proud of; it is the ceiling we are still trying to push down.