FlawPilot
From the blog

The Component Passed Review and Still Broke the Page

Quick answer: code review evaluates a diff, and a diff is the wrong unit for most frontend problems. A component can be correct on its own and still make the page it lands on slower, taller,…

The FlawPilot TeamSecurity research22 Sept 20269 min read

Quick answer: code review evaluates a diff, and a diff is the wrong unit for most frontend problems. A component can be correct on its own and still make the page it lands on slower, taller, noisier, or less accessible, because those are properties of the finished page and nobody on your team is assigned to look at one. The failures that survive review are exactly the ones that only exist after everything is composed.

Your component was good.

Props typed. Loading state handled. Tests pass. Storybook entry renders in isolation, which is the entire promise of Storybook. Two colleagues read the diff, left a comment about a variable name, approved it.

It shipped. The page got worse.

Nobody did anything wrong at any point in that sequence, and that's what makes this worth writing about. This isn't a discipline problem you can fix by reviewing harder. It's a unit problem.

A diff is the wrong unit

Here's the mismatch. Code review asks: is this change correct? That's a question about the change, answerable by reading the change.

But a good chunk of frontend quality isn't a property of any change. It's a property of the composition. Layout stability, total JavaScript, heading structure, how many fonts load, how many third parties get contacted, whether the main thread is free when someone taps: none of these live inside a component. They emerge when components sit next to each other on a real page, with real content, in a real browser.

You cannot see an emergent property by reading the thing that contributed to it. You can only see it by looking at the result.

So review keeps passing, correctly, and the page keeps drifting.

What slips through, specifically

Four patterns show up constantly, and each one is invisible at diff level by construction.

The component that is fast alone and slow together. Your carousel is fine. It's 40KB and it renders in a few milliseconds. So is the video embed, the testimonial slider, and the live chat widget. Each one passed on its own merits. Together they're 600KB of JavaScript competing for the main thread on a mid-range Android, and Total Blocking Time goes through the roof. No single PR caused that. Every single PR was defensible.

The component that pushes everything below it down. An image without dimensions. A font that swaps when it loads. A banner injected after hydration. In isolation this looks like a rendering detail. On the page it's Cumulative Layout Shift, and the user tapping "Continue" ends up tapping whatever slid into that spot instead. Layout shift is nearly always caused by one component and experienced by every component beneath it, which is precisely why the author never sees it.

The component that brings its own H1. In isolation, an <h1> in your hero is correct markup. But the page already has one from the layout, and the sidebar widget added a third. Now heading hierarchy is broken across the whole document, which affects screen reader navigation and how search engines read the page. Every individual component is defensible. The page is a mess.

The component that imports the world. Someone needed to format a date, so they imported a date library. The diff shows one import line and a one-line function call. It reads as trivial. What shipped is however much of that library survived tree-shaking, plus its dependencies, on every page that renders this component.

Notice the shape all four share. The diff is small, local, and correct. The consequence is large, distributed, and only observable after composition.

Why the tools you already have don't close the gap

It's worth being precise about this, because frontend teams often have quite a lot of tooling and still hit all four of the above.

Unit tests assert your component's behavior, which is the thing that was already fine.

Storybook renders it in isolation. That's its purpose and it's genuinely useful, but isolation is the exact condition under which these problems don't exist. A component cannot shift the layout of a page it isn't on.

Visual regression catches pixels changing, which is nearly the opposite of the problem. These pages often look right in a screenshot. Layout shift happens over time, during load, and a screenshot has no time axis.

Linters and type checkers work on source, not output. No amount of static analysis tells you the rendered document ended up with four H1s, because no single file contains four H1s.

None of these are bad tools. They're all correctly scoped to the unit they operate on. The page is simply a unit nobody's tooling is pointed at.

Nobody is assigned to the page

Here's the organizational version of the same gap.

Every component has an owner. Someone wrote it, someone reviewed it, and if it breaks, you can find them in the blame log. Ownership is clear right up to the component boundary.

The page, though, is composed. It's assembled from a layout, a handful of feature components from different teams, a shared header, an analytics snippet someone in marketing requested, and a chat widget that was added in a rush before a launch. It has contributors. It doesn't really have an owner.

Which means the question "is this page good?" is a question with no assignee. It gets answered implicitly, by whether anyone happens to complain.

And the degradation is gradual, which is the part that makes it hard to catch socially. No single deploy took the page from good to bad. Twenty deploys each took it from good to very slightly worse, and every one of them passed review, and at no point was there a moment where someone could reasonably have said no.

Review the page, not just the diff

The fix isn't more rigor in review. It's adding a second unit of evaluation, so the page gets checked the way the component already does.

Practically, that means periodically looking at the rendered result rather than the source. Not what you intended to ship: what actually assembled, on a cold load, over a real connection, in the state a visitor gets it.

That's the specific job FlawPilot does. It takes a URL and evaluates the finished page rather than the code behind it: Cumulative Layout Shift and Total Blocking Time on a cold load, heading structure and duplicate H1s, missing alt text, broken internal links, and the on-page SEO of pages nobody opens, alongside the performance, security, and infrastructure checks around them. Two minutes, no login. The useful part isn't any single finding, it's that the findings are attached to the page, which is the thing nobody had been evaluating.

A few habits help as much as the tooling:

  • Set explicit width and height, or an aspect-ratio, on every image and embed. This one change removes most layout shift at the source.
  • Decide where the <h1> lives, once, at the layout level, and make component headings start at <h2>.
  • Put a budget on the bundle and make exceeding it visible in CI, so "just one more library" becomes a decision instead of an accident.
  • Treat third-party scripts as changes to the page, because they are. A chat widget added without review is a deploy without review.
  • Check the rendered page on some cadence, not just after big releases. Gradual degradation needs a repeated measurement to show up at all.

What this changes about review

None of this means review should get heavier. It means review should stop being asked to do a job it structurally cannot do.

Keep reviewing diffs for what diffs are good for: correctness, clarity, edge cases, naming, whether this is the right abstraction. That's real work and review does it well.

Then check the page separately, on its own schedule, against the finished result. Two units, two checks, each pointed at what it can actually see.

Frequently asked questions

No, and that's the trap. Stricter review makes PRs slower without helping, because the reviewer still only has the diff in front of them. A reviewer cannot see that the page already has an H1, or that this is the fourth component importing its own date library.

Final Thoughts

The uncomfortable thing about this category is that every individual decision was reasonable. That's not a story about carelessness, it's a story about scope. Everyone did their job correctly at the level they were asked to work at, and the level nobody was asked to work at is where the page lives.

Components are reviewed because they have authors. Pages are what users actually get.

Pick your three most important pages and look at the assembled result this week. Not the code, the page. Most of what you find will be small, old, and fixable in an afternoon, which is exactly what you'd expect from problems that were never anyone's job to notice.

How FlawPilot helps

FlawPilot finds security and quality issues in your AI-built app and shows you how to fix them. It checks your deployed site across security, performance, infrastructure, and SEO, and scans your source code for vulnerabilities, hardcoded secrets, and vulnerable dependencies.

Every finding is prioritized and explained in plain English, with the actual fix: the configuration change, DNS record, security header, or code change needed. For supported findings, AI-powered guidance adds step-by-step instructions and suggested code fixes.

Connect your Git provider to scan your repository alongside your live site, so application findings, code vulnerabilities, secrets, and dependency issues all land in one place.

It fits your existing workflow too: a REST API for scores and findings, an embeddable security badge, and an MCP server so tools like Claude, Cursor, or ChatGPT can read your findings and help you work through them.

The boundaries are clear: the public website scan reads only publicly accessible signals, with no agent or credentials required, and source-code scanning is opt-in and read-only. Fixes are never applied or merged without your review.

Frontend DevelopmentCode ReviewCore Web VitalsCumulative Layout ShiftBundle SizeWeb Performance

Verify your AI-generated app is production-ready.

117 security checks in 60 seconds - free, no account needed.

Scan one page

Enter a URL - no account, no install.

Run a Site Health check

Requires a free account

Crawls every page we can reach and scores each one, so a slow template deep in the site stops hiding behind a healthy homepage.

Scan your source code

Requires a free account

Connect a Git provider to check for vulnerabilities, secrets, and risky dependencies.

Featured on