Good Code

A Modern Quality Pipeline and Testing Strategy for Frontend Projects

A practical guide to building a 15-layer frontend quality pipeline, from type safety to observability, and how to adopt it in phases so each layer pays ...

An AI agent writes most of my frontend code now. I review what it produces and tighten the architecture where it overreaches. That changes what a quality pipeline is for. You used to write tests and types so the next person on the file stayed sane. Now you write them so the agent can check its own work. Give it more ways to verify a change and it finishes more of the ticket on its own. A red check tells it what to try next.

Frontend has also grown more complicated since 2024. SSR, streaming, partial prerendering, server components, edge runtimes. Each adds a place where a change can break silently. TypeScript plus a couple of unit tests no longer covers it.

A quality pipeline is the set of checks that run on every change to give layered confidence the change is correct, accessible, performant, and safe to ship. A testing strategy is the part of that pipeline that asserts behaviour: what the app should do, at which level, at what cost. Plan them as one system.

This guide walks through a 15-layer pipeline adapted from alexop.dev's modern frontend quality pipeline, with my own take on which layers matter most and the order I recommend adopting them.

The Layers

Think of the pipeline as concentric layers. Each one is cheaper and faster than the one outside it. Run cheap checks first. Save the expensive ones for the things only they can catch.

1. Type Safety

Type safety is your first line of defence. Run your framework's type checker in CI on every PR. Treat any new type error as a build failure. With TypeScript that means tsc --noEmit or your framework's wrapper around it.

Validate untyped boundaries with a schema library like Zod or Valibot. Parse anywhere data crosses a boundary: route params, API responses, env vars, form input. TypeScript trusts the types you write. Schemas check that the data matches them at runtime.

2. Lint and Format

Catches style violations and a wide class of bugs without running the code. In 2026 the fast option is Oxlint or Biome instead of ESLint. Oxlint is roughly 50x faster, fast enough to run on every keystroke. I keep a lint-staged hook that runs Oxlint on staged files at commit time, and the full suite in CI.

Add two rule families regardless of which linter you pick: eslint-plugin-regexp for regular expression correctness, and @e18e/eslint-plugin for small performance lints that compound across a codebase.

3. Unit Tests

For pure functions, hooks, stores, and utilities. This is where most logic should live. Vitest is cheap and fast, runs on every save in watch mode, and runs the full suite in CI. Aim for high coverage of pure modules. Do not chase coverage on UI glue.

4. Component Tests

For components in isolation, with a real DOM and real user interactions. Vitest browser mode is the biggest win here. Your component tests run in a real Chromium instance via Playwright instead of jsdom. Hover states, focus, layout, intersection observers, and scroll behaviour all work as they do in production.

Pair this with Testing Library for whichever framework you use.

5. API Mocking

Hard-coded fixtures go stale. Tests that hit a real backend are flaky. Mock at the network layer once and reuse the same handlers everywhere. MSW (Mock Service Worker) is the tool for this. It intercepts fetch, XHR, and GraphQL with a service worker in the browser and a request interceptor in Node, so the same handler definitions work in Vitest, Vitest browser mode, Playwright, and the dev server.

6. Contract Testing

The mocks in layer 5 are only as good as the assumptions you bake into them. Contract testing ties the mock to a verifiable artefact that the provider checks against. There are three styles: consumer-driven (Pact), provider-driven (OpenAPI-based), and bi-directional (PactFlow).

Skip this layer if you own both services and ship them as one unit. Add it the moment consumer and provider deploy on different cadences, you do not own the provider, or a single backend serves multiple frontends.

7. End-to-End Tests

For critical user journeys across real pages. Keep the suite small. E2E is expensive. Playwright is the standard tool here. Run against a built preview, not the dev server.

Two assertions worth wiring into every E2E test: hydration mismatch detection (listen for hydration warnings on console) and CSP violation detection (listen for securitypolicyviolation events). These catch silent regressions that unit tests miss.

8. Accessibility

Accessibility cuts across lint, component, E2E, and preview. Check the same WCAG rule set at every cheap-enough stage. At lint time use eslint-plugin-jsx-a11y or eslint-plugin-vuejs-accessibility. At component level use axe-core. At E2E level use @axe-core/playwright. At preview use Lighthouse or Pa11y.

This is no longer optional in the EU. The European Accessibility Act took effect in mid-2025, so most B2C and many B2B products operating in EU markets must meet WCAG 2.1 AA conformance.

9. Visual Regression

Catches unintended UI drift that unit and E2E tests miss. Chromatic (hosted, Storybook-native) or Playwright screenshots with a diff tool like Lost Pixel. Gate on PR and review diffs as part of code review.

10. Performance and Bundle Size

Performance regressions are silent unless you measure them. Lighthouse CI on a preview deployment, run against both light and dark colour schemes. Use size-limit or your framework's bundle analyzer on PR for bundle deltas. Set explicit budgets and fail the build when they are exceeded.

11. Dead Code and Dependency Hygiene

Unused code is a tax on every other check. Use Knip to find unused files, exports, and dependencies. Use Renovate for automated dependency updates. Use OSV-Scanner for vulnerabilities and Gitleaks for secrets, gated to high-severity only to avoid alert fatigue.

12. Internationalisation Drift

If you ship in more than one language, untranslated strings slip through. Use a drift checker like Lunaria in CI that compares each locale against a source locale and reports missing or stale keys.

13. Preview Deployments

The cheapest way to enable manual review and to give E2E, Lighthouse, and visual regression checks something realistic to run against. Vercel, Netlify, or Cloudflare Pages will give you a unique URL per PR for free. Wire your downstream checks to that URL.

14. AI Code Review

In 2026 AI code review is a standard pipeline stage. It runs before any human reviewer touches the PR and catches issues the layers above miss. CodeRabbit, Greptile, and Vercel Agent are the main options. I have written about how this fits into an AI code review workflow before. The key is treating the AI reviewer as a high-recall first pass that reduces human review without replacing it.

15. Runtime Observability

Layers 1 to 13 give you confidence at merge time. Once a change is in production you also want a live view of what the app is doing. Use OpenTelemetry as the SDK. It is the only vendor-neutral option and the JS ecosystem caught up in 2025 and 2026. One collector with two exporters: point it at Jaeger in dev and Honeycomb or Grafana in prod.

Where Each Layer Runs

Each layer runs at the cheapest stage where it can catch the problem.

StageWhat runs
EditorType checker LSP, linter, Vitest watch
Pre-commitFormat and lint on staged files only
CI on PRTypecheck, full lint, unit, component, contract verify, build, Knip, size-limit, AI review
CI on preview URLE2E, accessibility (axe + Lighthouse), visual regression
Post-merge or nightlyFull E2E matrix, dependency updates, security scans
Dev server or productionOpenTelemetry traces and metrics

A note on local hooks: Lefthook has become the default modern alternative to Husky. A single Go binary, declarative YAML config, and parallel execution. Git 2.54 also added config-based hooks, which means a small project no longer needs an external hook manager.

Picking Your Adoption Order

You do not need every layer on day one. Here is the order I recommend:

  1. TypeScript strict plus a fast linter and formatter, wired into Lefthook
  2. Vitest for utilities, run on PR
  3. MSW handlers for any module that hits the network, shared between tests and the dev server
  4. Playwright for the single most important user journey, with hydration and CSP listeners wired into a shared fixture
  5. Contract testing the moment consumer and provider deploy on different cadences
  6. Preview deployments and Lighthouse CI (light and dark)
  7. OpenTelemetry traces and metrics
  8. Storybook plus visual regression once the design system stabilises
  9. Accessibility audits in component tests and E2E
  10. Knip and bundle-size budgets once the codebase has weight
  11. i18n drift checking once you ship a second locale
  12. AI code review once the project has external stakeholders

Each layer should pay for itself in caught regressions or saved review time. Remove the ones that do not.

Supply Chain Security

The pipeline above catches the bugs you write. None of it stops a compromised dependency from running code on your laptop. I use pnpm on every new project. Three settings do most of the work: lifecycle scripts blocked by default, minimumReleaseAge set to 1440 minutes (one day), and blockExoticSubdeps enabled. Pair this with OSV-Scanner and Gitleaks in CI.

The Agent Angle

All of this matters more when an AI agent writes your code. The agent gets a red check on a type error and tries a different approach. It sees a lint failure and fixes the pattern. It reads a test failure and adjusts its implementation. Without these layers the agent ships its first guess and you only discover the problem in production.

The pipeline is the feedback loop that makes autonomous coding viable. Every layer you add makes the agent more reliable and makes you more comfortable letting it run.

← Older
Actions Pattern in PHP

Newsletter

A weekly newsletter on React, Next.js, AI-assisted development, and engineering. No spam, unsubscribe any time.