Performance budget — catch heavy pages at build time

Every build weighs each page. A heavy page — an unoptimized hero image, a bloated inline script, a runaway HTML document — is the single biggest drag on load time, and it's cheap to catch at build time instead of weeks later as a bad Core Web Vitals score. StaticOwl runs a page-weight check on every compile and writes the result to /perf-budget.json next to your published pages.

This is a build-time weight check from data the build already has — not a full Lighthouse run (that would need a headless browser). It measures bytes, which is what you have the most control over on a static site.


What it measures

For every HTML page the build emits, the check records:

Field Meaning
htmlBytes Size of the HTML document itself.
assetBytes Combined size of the local assets the page references (images, scripts, stylesheets, the generated social card).
totalBytes htmlBytes + assetBytes — the page's transfer weight.
assetCount How many local assets resolved to a build file.
unresolved Referenced local paths that didn't resolve to a build file (a missing asset, or an off-site reference that can't be weighed). Informational.

Asset references are pulled from src= attributes, <link> tags for stylesheets, preloads and icons, and url(...) in inline CSS. Off-site URLs (https://…), data: URIs, and anchors are ignored — only local files that ship with your site are counted.

The check also flags any single image artifact that is over budget on its own, independent of which page uses it. SVGs are exempt from the image size check.


The budgets

Three thresholds, applied to every build:

Budget Default Applies to
HTML document 150 KB A single .html file.
Page weight 2 MB HTML + all of a page's local assets.
Single image 600 KB Any one local image file (SVG exempt).

A page or image that exceeds its budget produces a violation. Each violation records the path, the kind (page_html, page_weight, or image), the measured bytes, and the limit it broke.


The /perf-budget.json artifact

The report is emitted on every build and served at your site root, so you can fetch it the same way as any page:

curl https://<your-site>/perf-budget.json

Its shape:

{
  "generatedAt": "2026-10-16T12:00:00.000Z",
  "budget": { "maxPageBytes": 150000, "maxPageWeightBytes": 2000000, "maxImageBytes": 600000 },
  "pagesScanned": 42,
  "pages": [ { "path": "/", "htmlBytes": 48213, "assetBytes": 910044, "totalBytes": 958257, "assetCount": 11, "unresolved": 0 } ],
  "worst": [ "… top 10 pages by total weight …" ],
  "violations": [ { "path": "/gallery/", "kind": "page_weight", "bytes": 2411233, "limit": 2000000 } ],
  "totalViolations": 1
}

How to read and act on it

  1. Start with worst. Those are your heaviest pages. A high assetBytes points at images or scripts; a high htmlBytes points at the document itself (often a large inlined block or a very long page).
  2. Work the violations. A page_weight violation usually comes down to one or two oversized images — compress them, serve a smaller variant, or lazy-load below-the-fold media. An image violation names the exact file to re-export. A page_html violation means the document itself is too large — split the page or move large inline blocks to referenced assets.
  3. Watch unresolved. A non-zero count usually means a page references an asset path that didn't get built — a broken image or stylesheet link worth fixing on its own.

Report-only by default — or a hard gate when you opt in

Out of the box the budget check never fails a build. When a page is over budget, the build logs a warning with the count and a few examples, writes /perf-budget.json, and still publishes. Treat it as a signal to act on.

If you'd rather a heavy page block the publish, turn on enforcement:

With enforcement on, a build that has any violation fails and does not deploy — the over-budget pages come back as build errors (visible in the build result and in build_last / the build event), and /perf-budget.json is still written so you can see exactly what broke. Fix the pages, or raise the budget, and rebuild.

Two things enforcement never does: it won't block a rollback or replay of a previously-good build (those always re-publish), and it won't change what gets measured — the report is identical whether or not you enforce. If you'd rather gate in your own external pipeline instead, read /perf-budget.json after a build and fail on totalViolations > 0.


See also

Related docs

Resolved at build time via {% similar %} — cosine similarity over embeddings, not tag overlap. Zero arguments.

Accessibility — the WCAG build check & daily sweep
73% match
Publishing & environments
67% match
Deploy targets — where your published site goes
60% match