Writing
3 min read

Why I Built DevLens

Frontend performance work is reactive, manual, and easy to regress. DevLens turns audits, bundle analysis, and architecture review into one engine that fails the build when things get slower.

DevLensPerformanceArchitecture

Every frontend team I've worked on hits the same wall. Performance is everyone's job, so it ends up being no one's. A dashboard goes red weeks after the regression shipped, and by then nobody remembers which of forty PRs added the 200 KB.

DevLens is my attempt to make performance a build-time invariant instead of a quarterly cleanup.

The problem

The core issue isn't a lack of tools — it's that the tools are reactive. You find out something is slow after users do. The feedback loop looks like this:

  • ship a feature
  • someone notices the app feels heavy
  • open DevTools, dig through flame charts
  • guess at a fix, measure by hand
  • repeat

None of that is enforced. Nothing fails when the bundle grows or a layout shift creeps in.

Existing tools

There's no shortage of good individual pieces:

  • Lighthouse scores a page but doesn't attribute regressions to a diff.
  • Bundle analyzers show composition, but only when you remember to run them.
  • DevTools coverage / memory panels are powerful and entirely manual.

The gap

Each tool answers "how is it now?" — none answer "did this change make it worse, and by how much?" That question is the whole game in CI.

Pain points

The manual loop has three failure modes:

  1. Drift — small regressions accumulate below anyone's noticing threshold.
  2. No attribution — by the time a metric moves, the cause is buried in history.
  3. No gate — nothing blocks a merge, so performance is advisory, not required.

Architecture

DevLens runs the analysis engine once and feeds every surface — audit, bundle, memory, architecture — from the same normalized model. A source-mapped invariant gate compares against a baseline and fails the build on regression.

Components never touch raw API JSON — a PageSpeed or Gemini response is normalized through an adapter into a stable UI model first:

// adapters normalize noisy API shapes into one stable model
export function toAuditModel(raw: PageSpeedResult): AuditModel {
  return {
    score: raw.lighthouseResult.categories.performance.score * 100,
    metrics: pickMetrics(raw.lighthouseResult.audits),
    opportunities: rankOpportunities(raw.lighthouseResult.audits),
  };
}

The gate itself is intentionally boring — a diff against a committed baseline:

const regressed = current.totalBytes - baseline.totalBytes > BUDGET_BYTES;
if (regressed) process.exit(1); // CI fails, remediation PR opens

Lessons

A few things I got wrong first:

  • Normalize early. Letting raw API shapes leak into components made every provider swap a refactor. One adapter boundary fixed it.
  • The gate must be source-mapped. Byte counts without source attribution are noise — you need to say which module grew.
  • AI is a service layer, not a prompt in a component. Keeping inference behind a clean interface kept the UI testable.

Roadmap

Where it's going next:

  • GitHub App that comments the regression diff inline on the PR
  • architecture recommendations from the dependency graph
  • a VS Code extension for the same gate locally

If you've felt the same reactive-performance pain, DevLens is the shape of the fix I wanted to exist.