Skip to main content
NewNMC’s advertising guidelines for doctors are in force from 6 Oct 2026. See what to change, and get a free compliance auditNew NMC advertising rules for doctors: what to change
Website

Performance Budgets for Healthcare Sites: Setting and Enforcing Limits in CI

How to set JavaScript, image and LCP budgets per page template and fail a Lighthouse CI build when a pull request exceeds them, with a step-by-step plan for health tech teams.

Performance Budgets for Healthcare Sites: Setting and Enforcing Limits in CI

This post is for the engineering lead or founding developer at a health tech startup, or the agency developer maintaining a clinic or hospital site, who has watched page weight creep up release after release. Nobody decided to ship a 3 MB doctor profile page. It arrived one chat widget, one carousel and one unoptimised hero image at a time.

A performance budget stops that drift by turning "keep the site fast" into a build check. It answers three questions: what limits to set, how to set them per template instead of one number for the whole site, and how to make the build fail when a pull request breaks them. The examples use Lighthouse CI, since that is what we run against our own Next.js builds, but the logic carries to any tool that can assert on resource sizes and timings.

It is not a replacement for field data. A build check measures a synthetic load on a build server; the people using your site are on varied phones and networks. Read this alongside the broader healthcare website speed and Core Web Vitals guide, and treat the budget as the guard rail that stops regressions, not as proof the site is fast.

What a budget is, and what it cannot do

A performance budget is a set of numeric limits that a page or template must stay inside. Typical limits fall into three kinds:

  • Quantity limits: bytes of JavaScript, bytes of images, number of third-party requests, number of fonts.
  • Timing limits: lab Largest Contentful Paint, Total Blocking Time, Cumulative Layout Shift.
  • Rule limits: no render-blocking third-party script in the head, every image has dimensions.

Quantity limits are the most stable because they do not vary with runner load. Timing limits in a CI environment are noisy, so they need several runs and a sensible aggregation. Rule limits catch specific mistakes.

One important limitation: Lighthouse runs in a lab and does not tap or type, so it cannot report Interaction to Next Paint. Total Blocking Time is the usual lab proxy for main-thread congestion, but it is a proxy. Keep a field check for INP using the method in web.dev's INP documentation, which sets 200 milliseconds at the 75th percentile as the good threshold. The budget prevents the obvious regressions; field data catches what the lab cannot see.

Step 1: Group pages into templates

A single budget for the whole site is either too loose for the home page or too tight for a page that legitimately carries a map and a booking widget. Budget by template. A health tech or hospital site normally has five to eight:

TemplateTypical contentsWhat tends to bloat it
HomeHero, service cards, searchHero image, carousel, video
Service or specialty pageText, FAQ, doctors, formForm libraries, embeds
Doctor profilePhoto, bio, bookingBooking widget, review widgets
Listing or directoryMany cards, filtersDOM size, client-side filter code
Blog articleText, imagesInline images, share widgets
Booking or checkoutForm, paymentPayment SDKs, validation libraries
Location pageMap, hours, directionsMap SDK

Pick one representative URL per template, or two for the templates that vary most (a doctor with a long bio and a doctor with a short one). Store these in a list the build can read. The budget is only as good as the pages it measures, so include a page with realistic content, not a nearly empty staging page.

Step 2: Set starting numbers by measuring, not guessing

The commonest error is to import a number from an article. Instead, measure the current build for each template, then set the budget slightly above the current figure for templates you consider healthy, and at your target for templates that are already too heavy, with a plan to reach it.

A practical routine:

  1. Run Lighthouse CI against each template URL with at least five runs, and record the median.
  2. Record the resource-summary figures: script bytes, image bytes, third-party request count, total.
  3. Compare with field data for the same template from CrUX or your own beacons. If the field LCP is poor, the lab is probably also poor, and you tighten the budget.
  4. Set the first budget at the current median plus a small margin for noise on healthy templates. Lower it in steps as work lands.

Illustrative starting limits for a modest site (hypothetical, for structure only, not benchmarks): a blog article might allow 150 KB of JavaScript and 400 KB of images; a listing page might allow 250 KB of JavaScript because of filtering code, but a lower image budget since cards use small thumbnails; a booking page might allow more JavaScript but zero third-party scripts beyond the payment and analytics providers. The relative ordering is what matters. Your own numbers come from your own measurements.

For LCP, Google publishes thresholds for field data, and those are the targets for real users. In CI, set the LCP assertion with headroom on the throttled lab profile, take the median of several runs, and rely on field data for the verdict.

Step 3: Express the budget in Lighthouse CI

Lighthouse CI's configuration is documented in the Lighthouse CI configuration reference. The pieces we use are:

  • collect.numberOfRuns: how many times each URL is tested. The CLI default is 3; use 5 for timing assertions to reduce noise.
  • assert.assertMatrix: an array of assertion sets, each with a matchingUrlPattern, so each template gets its own limits.
  • preset: a starting assertion set (lighthouse:all, lighthouse:recommended or lighthouse:no-pwa) you can extend.
  • Resource-summary assertions in the form resource-summary:<resourceType>:(size|count), for example resource-summary:script:size or resource-summary:image:size.
  • maxNumericValue for the limit and aggregationMethod for how multiple runs combine. The documented options are median, optimistic, pessimistic and median-run, and the default is optimistic.

One unit trap: the documentation notes that maxNumericValue for file size is in bytes, while a budget.json file uses kilobytes. Also, budgetsFile cannot be combined with other assert options, so if you want timing and size assertions together, use assertions rather than a budgets file.

A configuration shape, described rather than reproduced as a code block: the top-level object has a ci key with collect and assert sections. In collect, list the template URLs and set numberOfRuns. In assert, define an assertMatrix with one entry per template. Each entry has matchingUrlPattern (a regular expression such as the path prefix for doctor profiles) and an assertions object. For the doctor profile template that object contains resource-summary:script:size set to error with a maxNumericValue in bytes, resource-summary:image:size likewise, resource-summary:third-party:count as a warn, and largest-contentful-paint as an error with a maxNumericValue in milliseconds and aggregationMethod set to median. For the blog template, keep the same keys with tighter script and image values.

Two practical notes. First, set most quantity assertions to error and most timing assertions to warn at the beginning. A flaky timing failure that blocks every merge teaches the team to disable the check. Promote timing assertions to error after a few weeks of stable results. Second, use matchingUrlPattern carefully: if two patterns both match a URL, both sets apply, so make patterns specific.

Want this applied to your practice? Get a free website audit.

Book my free audit

Step 4: Run it in CI where it can fail a build

The check should run on every pull request, against a production build served locally or against a preview deployment. Preview deployments give more realistic headers and compression; a local server is simpler and less flaky. Either way, the pipeline should:

  1. Build the application in production mode.
  2. Start it and wait until it responds.
  3. Run lhci autorun, which collects and asserts in one step.
  4. Fail the job if any error assertion fails, and post the report as an artifact or a pull request comment.

Make the failure message tell the developer what to do. "Script size 312 KB exceeds 250 KB" is actionable. Include the template name and a link to the report. If the team cannot tell why a build failed, they will route around the check.

Add a bundle analysis step for script budgets. When the script limit is breached, the developer needs to know which module caused it. Next.js projects can use a bundle analyser, and many teams compare against a stored baseline. Keep that baseline in the repository so the diff is reviewed.

Step 5: Handle exceptions without losing the budget

Budgets that never bend get disabled. Budgets that bend silently stop meaning anything. A workable policy:

  • A pull request that breaches a budget needs a written reason in the description and a reviewer who owns performance.
  • An approved exception is recorded as a change to the budget file, with a date and a ticket for paying it back, so it shows in the history.
  • Third-party additions (a new chat tool, a new pixel) require a before-and-after run on the affected templates. In a healthcare setting that also triggers a data protection review, because a new third-party script may receive data about visitors. That review is a separate decision for the provider's compliance owner.

Re-baseline quarterly. If three templates are well under their limits, lower the limits so the headroom does not become the new allowance.

Step 6: Connect the budget to field data

The budget is a prediction. Check it against what happens. Each month, compare the CI numbers for each template with field results from CrUX or your own beacon data, and ask whether a template that passes CI is still poor for real users. If so, the lab profile does not match your audience, perhaps because most visitors are on mid-range Android phones on mobile data. Adjust the throttling settings, add a template, or add rules for the specific cause.

Common mismatches: a consent banner that appears only for real visitors, a personalised element that CI never sees, a logged-in portal state, and third-party scripts that behave differently in production. For each, add a test URL or a scripted state that reproduces the case.

A starting checklist

ItemDecision
TemplatesList five to eight, one URL each (two for variable ones)
RunsFive per URL for timing, median aggregation
Quantity limitsScript bytes, image bytes, third-party count, per template
Timing limitsLCP and Total Blocking Time as warn, then error
Field checkCrUX or beacons for INP, since Lighthouse cannot measure it
Failure messageTemplate, metric, limit, actual, report link
ExceptionsWritten reason, dated entry in the budget file
ReviewQuarterly re-baseline

If you want help with the wider performance work these checks protect, see our Core Web Vitals service and the site speed optimisation page. The page speed glossary entry is a useful primer for the non-engineers who will read the reports.

Filed underperformance budget lighthouse CIlighthouse ci assertionsperformance budget Next.jsJavaScript budget by templateCI web performance health tech

About the author

Co-Founder & CTO · Gurugram, India

Arush builds the systems the rest of the company runs on — the patient-acquisition pipelines, the AI chatbots, the analytics that tie a single click to a booked appointment. If a campaign reports a number, it's because something his team built is tracking it.

Free guide · 15 pages

WhatsApp AI Chatbot for Hospitals (14-Day Deploy)

The 14-day WhatsApp booking-bot implementation playbook.

The Pioneers Brief — one essay, three benchmarks, one teardown, every Thursday. Subscribe free →

Free website audit

Your next chapter starts with a conversation.

Three quick questions. We come prepared with a plan for your specialty and city.

  • A 30-minute call with a healthcare specialist
  • Your top three growth opportunities, in writing
  • Honest advice — even if that means not hiring us
  • No obligation and no hard sell
You’ll speak with a senior strategist
Consultations are hosted by Arush Thapar’s team.
Step 1 of 3Takes 30 seconds

What do you want to improve first?

Details stay privateNo obligationReply within 1 working day

Client portfolio · Paid advertising

Work relevant to performance budgets for healthcare sites setting and enforcing.

Explore client work →