A performance budget before I knew the name

A slow phone and an expensive connection turned page weight from a technical afterthought into a product constraint.

I first tested C62Y on a phone by holding it beside my laptop and deciding that the smaller layout looked good. Months later, I tested it as a person might actually use it: on an ordinary device, away from Wi-Fi, with an empty cache.

The result took long enough to make the responsive layout feel like a joke.

The catalogue had adapted to the screen but not to the connection or processor. Its product photographs were oversized. Its icon set was a collection of separate requests. A general-purpose plugin loaded on every page to power one control on the comparison view. Web fonts delayed readable text. The page was visually considerate and operationally indifferent.

I did not know the phrase “performance budget” yet. I made a spreadsheet.

The test had to stop flattering the page

My laptop test began with warm caches, a fast processor, a local network, and developer familiarity. I knew which control I intended to press and mistook my own anticipation for responsiveness.

I chose one ordinary phone as the reference device and wrote down a repeatable route: open the catalogue from a cold browser, find a category, inspect one product, add it to the comparison view, and return. I cleared the cache between runs and used a constrained connection rather than standing beside the local server.

The timings were imperfect. Browser tools and network shaping were less convenient than I wanted, and one run could vary considerably. Repeating the same task under the same conditions still revealed the shape of the delay. The page spent too long blank, became visible before it responded reliably, and downloaded resources for interactions the route never used.

I recorded landmarks a person could perceive:

first readable heading
first useful product row
first image supporting identification
comparison control responds
scroll remains continuous

The list kept the exercise grounded. A lower transfer total was valuable because of what it changed along that path, not because a small number looked virtuous in isolation.

Inventory before optimization

The spreadsheet listed every transferred resource by type, compressed size, and reason for existing. I added request counts because connection setup and latency mattered as much as the total bytes on a weak mobile network. The first useful outcome was not a faster page. It was embarrassment with labels.

The first catalogue page looked roughly like this after compression:

Resource groupInitial transferRequestsWhy it arrived
HTML18 KB1Product and navigation structure
CSS46 KB4Base layout, catalogue rules, plugin styles
JavaScript112 KB7Navigation, comparison panel, utilities, plugins
Fonts138 KB4Two families and several weights
Images684 KB13Product grid, feature image, icons
Total998 KB29First useful catalogue view

These were not enormous numbers by every desktop comparison. On the reference path, the combination of latency, decoding, parsing, and main-thread work was enough to make the page feel absent. The table also excluded later gallery requests, so it did not describe the whole session.

I added an owner and purpose beside each row in the working sheet. “Plugin” was not a purpose. “Upgrades the already-readable comparison section into a dismissible overlay” was. Resources without a sentence became deletion candidates.

The inventory distinguished initial, deferred, and conditional cost. A gallery image loaded after someone opened the gallery belonged to a different decision from a gallery image blocking the category heading.

“JavaScript” was too broad to argue about. “A library used only to open one panel” was specific. “Images” sounded inevitable. “A 1400-pixel photograph displayed at 280 pixels” was a decision.

I created limits for the initial catalogue view:

  • HTML and critical CSS had to produce a readable page without waiting for script.
  • The first product image had a strict size target and appropriate dimensions.
  • Nonessential photographs loaded after the primary content.
  • JavaScript needed a named interaction that justified its transfer and execution.
  • The number of initial requests could not grow casually.

The numbers were based on the page I could make acceptably responsive on the test device, not on an industry score. A budget only helps when violating it forces a real tradeoff.

The first limits were deliberately round and local to this page:

Initial resourceWorking limit
Total transfer450 KB
Requests before useful content16
JavaScript55 KB
Fonts70 KB
Primary visible imagery240 KB

I did not present those values as an industry standard. They described what the reference device could receive and process while the task still felt coherent. A different page could spend differently. The important rule was that the totals could not creep upward through individually reasonable additions.

The sheet showed remaining allowance as well as current weight. Negative space made cost visible before a change shipped. A proposed 40 KB script was not “only 40 KB”; it consumed most of the remaining script allowance and needed to justify that choice.

I kept a small reserve rather than filling every line to its exact ceiling. Compression can vary, content changes, and a budget with no room encourages arguments over measurement noise instead of product value.

Combining small icons into a sprite reduced requests, and compressing assets reduced transfer. Those changes mattered. The surprising delay came from work after download.

One plugin searched and decorated much more of the document than the interaction required. Another script measured layout repeatedly during startup. On a laptop the cost disappeared into a fast processor. On the phone it occupied the main thread while the user was trying to scroll.

I removed the general-purpose panel plugin and wrote the small behavior directly. The control was an ordinary link to content already present in the document; JavaScript upgraded it into a disclosure. This was less code, but more importantly it had a reliable no-script state.

The font decision was harder. The chosen web font contributed to the catalogue's identity, yet it delayed the moment at which model names and specifications became comfortably readable. I reduced the number of font files and weights, selected a close system fallback, and stopped hiding text while the font loaded. The fallback appearing briefly was preferable to an empty interface pretending typography mattered more than information.

The inventory said what transferred. The request waterfall showed what waited for what.

One stylesheet imported another stylesheet, which referenced font files discovered only after the CSS arrived. A script in the document head blocked parsing before most catalogue content existed. Product images competed with fonts and icons even though only the first few were visible.

The page was not simply heavy. It had arranged its spending in the least useful sequence.

I inlined only the small structural rules required to make the top of the catalogue readable and moved the rest into one external stylesheet that could be cached. I removed CSS @import so the browser discovered the main rules directly. Scripts that upgraded existing markup moved after the content and ran without blocking its parse.

The HTML carried real headings, links, product names, and comparison content. That meant the useful path could begin before JavaScript completed. Script enhanced navigation and disclosure behavior rather than releasing hidden content from an empty shell.

I watched for the opposite mistake: inlining everything to win a cold first view. Shared external assets paid off on the next page, which was part of the catalogue task. The budget covered the route rather than optimizing one screenshot at the expense of navigation.

Changing discovery order produced a larger perceived improvement than shrinking several already-late resources. Sequence was part of the interface.

Images needed a content policy

The catalogue had accumulated one high-quality product photograph per item and relied on CSS to make each image fit its card. The browser still downloaded the large source. Responsive layout had changed display size without changing transfer size.

I could not assume a dependable responsive-image markup solution in the target browsers. The build produced a small set of derivatives and the initial catalogue used a conservative image appropriate to its narrow card. A link to the product page could request a larger asset when the image became central to the task.

The first visible rows received actual image dimensions in markup so layout did not jump after download. Lower rows used a simple deferred loader layered on top of meaningful alternate text and product links. I was cautious about making identification depend on a script-triggered image, so the default content order and labels remained useful before every photograph appeared.

The feature image was the hardest cut. It set a mood and delayed every product decision. I replaced it with a smaller crop on the catalogue landing view and preserved the larger treatment on a page where someone explicitly chose the story.

Compression became a per-image judgment. Flat product diagrams tolerated different settings from photographs with labels. The earlier overcompressed labels proved that one quality number was not a policy.

I recorded derivative dimensions and approximate transfer ranges in the content workflow. Performance stopped depending on somebody remembering to resize an image manually before upload.

JavaScript paid twice

The spreadsheet initially tracked compressed transfer and made a 30 KB plugin look cheaper than a 45 KB image. On the phone, the script also had to be parsed and executed on the main thread before its interaction worked.

I used simple instrumentation around initialization and repeated the task with scripts disabled one at a time. The goal was not laboratory precision. It was to find work whose cost was disproportionate to its product role.

One selector ran across the entire document to decorate links that existed in one comparison region. Scoping it reduced startup work. A touch helper installed listeners on every product row even though ordinary links already handled the action. Removing it improved scroll behavior and keyboard consistency at once.

The largest win was deleting a general-purpose overlay plugin. The comparison content already existed in the document at a fragment target. A small script added disclosure and focus behavior where supported. Without script, the link still navigated to the section.

That replacement was not merely fewer bytes. It reduced style rules, DOM wrappers, layout measurements, and failure modes. Transfer size was a useful proxy for some costs, but ownership and execution determined the rest.

I started listing initialization work beside script weight. A file could fit the byte limit and still spend too much of the first interaction on synchronous setup.

Budgets changed conversations

Before the spreadsheet, requests for additions were framed as yes-or-no design questions. Could I add a rotating feature panel? Could every category have a large header photograph? Could the comparison view include a richer zoom control?

Afterward, each request had an opportunity cost. A new asset did not need to be forbidden, but it needed to displace something or justify a higher budget. The catalogue became a finite product again.

The rotating feature panel was the clearest example. Its proposal included three large images, an animation script, controls, and copy that duplicated category links below it. Looked at alone, each element was defensible. Looked at against the first-view budget, the panel consumed most of the image allowance before showing a product.

I kept one static, lightweight feature and used the recovered space for clearer category summaries. The outcome was less fashionable and more useful on the reference path.

The comparison zoom request survived. Reading small product labels was central to comparing items, and the interaction could load its large image only after a person asked for it. It spent more total bytes in that branch without delaying everyone.

This distinction prevented the budget from becoming a universal austerity rule. Important capabilities could cost more when their cost was conditional and proportionate to intent. Decorative defaults faced a higher bar because every visitor paid for them.

This was especially useful because performance work otherwise arrived at the end. A finished page would be declared slow, and engineering would be asked to preserve every decision while somehow removing its cost. The budget moved the constraint to the moment when choices were still cheap.

I did not have sophisticated continuous measurement. A checklist recorded the key weights, and I repeated cold-load tests on the same device and connection conditions. Crude consistency was better than a precise dashboard nobody owned. Later projects automated this kind of gate, but the practice came before the tooling.

The release checklist captured cold transfer totals, request count, and the five perceptual landmarks on the reference device. It stored a screenshot of the waterfall and the sheet version beside the release notes. This was manual enough to be skipped, so the checklist named one person responsible for running it before publishing.

When a number exceeded the allowance, the release did not automatically fail forever. The change received a note explaining the added value, the displaced cost, or the reason the budget itself should change. That made exceptions visible rather than converting the sheet into a ceremonial target everybody quietly ignored.

A short text comparison was surprisingly effective:

previous: 438 KB / 15 initial requests
candidate: 472 KB / 16 initial requests
change: new category image +48 KB, removed icon request -2 KB
decision: resize image derivative before release

The process was primitive continuous governance. It prevented performance from depending on the memory of the person who happened to be most concerned that week.

I optimized the wrong thing once

One image pass became too aggressive. Product labels were crisp on my screen but hard to read on a denser display. I had met a byte target by weakening the content. That was not a performance win; it was a transfer of cost from the network to the user.

I corrected it with several image derivatives and a slightly larger default. The lesson was important: a budget is a boundary for judgment, not a substitute for it. The fastest version of a page is the version that does not exist. A useful product must spend resources. The work is to spend them deliberately.

I also learned not to celebrate aggregate page weight while ignoring sequence. A secondary gallery could be large without delaying the first decision if it loaded later. A tiny blocking script in the document head could cause more harm than a larger optional resource. What arrived first and what occupied the main thread mattered alongside total bytes.

A passing budget could still feel slow

After the first reductions, one build met every transfer limit and still responded poorly when the comparison panel opened. The interaction triggered several synchronous measurements and style changes that caused repeated layout work.

The budget had constrained delivery, not every runtime behavior. I added a task check: after the page became readable, the first comparison action needed to respond without a visible pause on the same phone. I recorded the behavior with a simple timeline and counted layout reads interleaved with writes.

Batching reads before writes and removing an unnecessary height animation improved the interaction. The panel could use a simpler transition that did not measure every intermediate frame.

I resisted inventing one aggregate performance score. Transfer, readiness, scroll continuity, and interaction response were related but not interchangeable. The budget acted as a guardrail; the task path remained the final evaluation.

This also exposed a false success from deferred loading. Moving expensive code later made the initial view faster, but if it ran exactly when someone tapped compare, the delay had merely moved into their action. Conditional work still needed an appropriate loading and execution strategy.

The test was not “did I postpone it?” It was “did the person receive value before and after choosing to pay the cost?”

Cache warmth was a separate experience

The cold visit deserved emphasis because it had been neglected. The catalogue also involved several page transitions, and shared assets should become cheaper after the first load.

I checked both. A versioned external stylesheet and compact shared script could be cached across categories. Product images were specific to their pages. The release process gave changed assets new names so a long-lived cache would not serve mixed versions.

I did not let a warm-cache result excuse the first view. A person could arrive directly at any category from a link. Nor did I let cold optimization duplicate shared CSS into every page and make the route heavier overall.

This is where the spreadsheet gained route totals. It tracked the entry page and one representative navigation path. An asset could cost more on entry and save more across the route, but the tradeoff had to be visible.

Cache behavior was part of product performance, not a reason to report the most flattering run. I labeled every measurement cold or warm and stopped averaging the two into a number that described neither.

Performance is interface behavior

The industry often described performance as an engineering quality, adjacent to security and maintainability. On C62Y it was directly visible product behavior. A link either responded while someone stood in a workshop, or it made them wait. A specification either appeared before they lost confidence, or it did not.

The budget did not make the site instant, and I would not trust a retrospective that claimed it did. It gave me a durable way to prevent accidental growth. More importantly, it made constraints discussable across design and engineering. Images, typography, scripting, and content hierarchy belonged to the same decision.

The first view fell from roughly one megabyte and twenty-nine requests to just under the working transfer limit and fifteen initial requests. Those totals varied with image content and compression, which is why I remember the boundary more than one victorious number. The reference route became readable earlier, scrolling stopped fighting initialization work, and the comparison action no longer depended on a large plugin.

The remaining limitations were real. Testing centered on one reference phone and a small set of connection conditions. Manual measurement could miss regressions. Deferred galleries still became expensive when used. Font behavior varied between browsers. The budget was a disciplined local practice, not proof that every visitor received the same experience.

Its most durable effect was editorial. A page could no longer acquire weight without acquiring a reason. Content, imagery, code, and typography competed for the same finite opening moment.

I started the spreadsheet because a phone embarrassed me. I kept the practice because it turned speed from cleanup into authorship.

The numbers changed later. The obligation to explain what entered the opening path did not.