We built a Lighthouse skill that's not allowed to break the stack
We built a Claude Code skill that audits any site with Google Lighthouse and produces a fix plan with one hard rule: it must never change how the project renders. The pattern of treating your architecture as a machine-checkable invariant is the bit you can steal.
We've just added a new skill to our Claude Code setup called /fuckingfast (naming things is hard; naming them honestly is easier). Run it in any project and it audits the site against Google Lighthouse, works out what's slow, and produces a ranked fix plan with one hard constraint baked in: it is forbidden from proposing any fix that changes how the project renders.
(skip to the bottom to download the skill and copy it to ~/.claude/fuckingfast/SKILL.md)

That constraint is the interesting part, and it's the bit you can steal.
Why "just make it faster" is a dangerous instruction
We run a portfolio of a few dozen sites and apps, and almost no two are built the same way. Some are plain static HTML. Some are Vite single-page apps (several inherited from Lovable and deliberately left as-is). Some are Next.js with a mix of statically generated, ISR and server-rendered routes on the same site.
Generic performance advice ignores all of that. And an AI coding agent given the goal "improve the Lighthouse score" is worse than generic advice, because it will happily act on it: converting client components to server components, bolting caching onto routes that shouldn't have it, or quietly restructuring an app that was fine as it was. The score goes up; the architecture you chose on purpose goes away.
We'd also been bitten before by how silently rendering models break. One earlier gotcha: in the Next.js App Router, calling cookies() anywhere in a page's data fetch flips the entire route from static/ISR to per-request dynamic — no build warning, no error. We only spotted it because metadata started streaming into the <body> and Lighthouse complained about a missing meta description.
The pattern: fingerprint first, then treat the architecture as an invariant
So the skill's first job isn't running Lighthouse at all. It's establishing what must not change:
- Fingerprint the stack. Read the project docs, then verify against the code:
package.json,next.config, whether there's a bareindex.html, what the host config says. Classify every page as static HTML, client-rendered SPA, prerendered Next.js, or dynamic Next.js. - Record a machine-checkable invariant. For Next.js this is beautifully concrete:
npm run buildprints a route map with markers —○static,●SSG/ISR,ƒdynamic. The skill records that map before touching anything, and after every single fix the build must produce the same map. If a marker flips, the fix gets reverted, no discussion. - Filter every finding through the fingerprint. Lighthouse says "unoptimised images"? On a static site that means
<picture>and WebP; on Next.js it meansnext/image(after checking the image config). "Unused JavaScript" on Next.js explicitly does not license converting components between server and client just to move a number. - Show the plan, then stop. Scores, ranked fixes (impact, effort, risk, and why each one is safe for this stack), plus an explicit list of what will not be touched. Nothing changes until we say yes. On a yes it writes the plan to a checklist file in the project root and works through it one fix at a time, re-testing after each.
Details that turned out to matter
Never audit a dev server. Dev builds are unminified and carry hot-reload overhead; their scores are noise. The skill tests the live production URL where one exists (real CDN, real compression headers), and otherwise builds and serves a production bundle locally while flagging that local numbers are only useful relative to each other.
One fix per iteration, with a revert rule. Each fix must pass the build, keep the route map identical, and show its before/after in a re-test log. If it regresses the score or breaks the build and the repair isn't mechanical, it gets reverted and parked with a written reason. This keeps the agent from digging holes.
The skill is just a Markdown file. For anyone who hasn't built a Claude Code skill: it's a SKILL.md with YAML frontmatter in ~/.claude/skills/<name>/. The frontmatter description doubles as the trigger, it's how the agent decides the skill applies — so it's worth writing as a dense summary of behaviour, not marketing copy. The body is essentially a standing operating procedure the agent follows step by step. Ours is about 70 lines.
The Lighthouse invocation itself is nothing exotic:
lighthouse https://example.com --output=json --output=html \
--chrome-flags="--headless=new" --quiet
Mobile and desktop, homepage plus one or two representative pages — a blog post, a main app screen, because the homepage is rarely where the real problems live.
The takeaway
When you hand an agent an optimisation goal, hand it a definition of "don't break anything" in the same breath, and make it measurable, not vibes. "Preserve the architecture" is a wish; "the build's route map must be byte-identical after every change" is a test the agent can run itself. Most stacks have an equivalent invariant lying around: build output, a bundle manifest, a schema dump, a snapshot test. Find yours, record it before the agent starts, and check it after every change.
An agent that knows what it must not change is far more useful than one that's merely told to make things better.
Enjoyed this? Get Hurricane Signal
Field notes on building real things with LLMs — occasional, practical, no hype.