When a website first launches, it often feels fast. It has few pages, limited images and only a handful of small components. Over time, analytics scripts, chat tools, large images, new fonts and interactive widgets arrive. Each change can look reasonable on its own, but together they make a visitor wait for more network requests, JavaScript parsing and main-thread work before seeing useful content.
A performance budget makes speed measurable: limits for JavaScript on a critical route, image weight, third-party requests, initial network work and the stages when content becomes visible or interactive.
Start with the user journey
Choose routes where people most often begin or where business value is highest: a home page, project detail, blog listing, sign-in or checkout. For each, identify meaningful content and the next expected action.
For a portfolio or content site, the first goal may be to show a readable page structure quickly. For an administration tool, the key goal may be making a record editable. The same technical metric can mean different things in different products. A budget should therefore express “the user can do this task without delay” rather than “achieve the highest score”.
Keep the first budget ambitious but practical. For example, define separate limits for downloaded JavaScript, a primary image and third-party requests on an initial route. A single total byte limit hides the difference between small but expensive JavaScript and a large image that decodes quickly.
Find the critical path and defer unnecessary work
Every resource that does not contribute to the first view is a candidate for extending the critical path. A map below the fold, comments, a detailed chart or an editor used rarely does not need to run for every person during initial load. Deferring these components until interaction, viewport proximity or later navigation is often a better trade-off.
In React applications, the boundary of client components matters particularly. Making a large page section run on the client for one small interaction sends unnecessary JavaScript. Static content and views that can be prepared on the server should stay light where possible. A dynamic part should be small, independent and loaded later when it is truly needed.
Code splitting is not only a framework setting. It requires regularly asking where a component loads, how large shared dependencies become and what a visitor downloads for a feature they will never use.
Treat images as part of content strategy
Images are the largest weight on many content sites. The answer is not removing all images, but choosing a source appropriate to each image’s role. A title image visible in the first viewport needs correct dimensions, a modern format, explicit size information and, when necessary, priority loading. Images further down should instead use lazy loading and responsive sources.
Sending the same high-resolution file to a desktop and a small screen adds needless cost, especially on mobile networks. Width descriptors and alternative sources let the browser choose an appropriate asset. Supplying image width and height, or equivalent reserved space, also reduces layout shifts during loading.
Decorative images need no alternative text, while meaningful images need descriptive alternatives. Performance and accessibility meet in the same design decision: what is displayed, why it is displayed and when it loads should be clear.
Control fonts and third-party code
A new font family or weight can be an aesthetic choice, but it also carries network and rendering cost. Limiting styles to what is used, serving only needed character sets and keeping critical text visible until a font arrives protects the experience. Four font files from different design tools rarely create better typography; they create a slower first view.
Third-party scripts need even more care. Analytics, support widgets, A/B testing tools and social embeds bring their own network requests, JavaScript cost and privacy effects. For every integration, ask: what user value does it provide, is it needed during initial load, and does the page still work if it fails?
Start third-party code after the main content loads or after user interaction whenever possible. Removing an integration is sometimes the most effective optimisation; a performance budget makes that decision discussable.
Use caching and static generation
For pages that do not change frequently, static generation can serve content through a CDN without server computation per request. Caching versioned HTML, CSS, JavaScript and image assets helps clients download only changed files after a new release.
Avoid two extremes in caching strategy. A very short cache repeats the same cost on every visit; a very long, unversioned cache can show stale content. Content hashes in asset names together with more controlled HTML refresh rules are a common way to establish this balance.
Check budgets in CI and real usage
A page that feels fast on a developer laptop can feel different on a low-powered device or slow connection. Use laboratory measurements and real-user data separately but together. Lab testing enables repeatable comparisons between changes; field data reveals variation in actual devices, networks and behaviour.
At build or pull-request time, thresholds for bundle size, per-route resource count and basic performance measures catch regressions early. Failing the pipeline immediately on every budget overrun may not suit every team; beginning with a warning and a documented exception can be more practical. The important part is that an exception is visible and temporary.
Performance work is not a one-time cleanup. Every new component, image and integration takes a share of the budget. With clear limits connected to user journeys, architectural choices that reduce the critical path and regular measurement, a fast site becomes not an initial accident but a maintained engineering property.