Skip to main content
All writing

A production-readiness checklist for vibe-coded Next.js apps

Eleven checks that catch the defects automated scans and code review miss, each one drawn from a real failure in a Next.js application that already passed its tests.

Every item below comes from a defect that survived a passing test suite, a green build, and a person reading the diff. None of them were exotic. They survived because nothing looked for them.

Run these against a production build, served, in a browser. Most of them are invisible in development.

Status codes, not just content

Ask for a URL that should not exist and read the status line, not the page.

A page can render a perfectly good "not found" screen and still answer 200. In one case a loading boundary on a shared route segment meant the framework sent a streamed shell before the not-found decision resolved, so every route beneath it answered 200 on its not-found path. Without JavaScript the page never resolved at all.

curl -s -o /dev/null -w "%{http_code}\n" https://example.com/en/does-not-exist

Also check what an unmatched address under a localized prefix actually renders. If it falls through to the framework's built-in error page you lose the document language, the main landmark, your translations, and your styling all at once.

Forced colors, in a browser

Windows high-contrast mode replaces your colours with system colours. A filled button whose label resolves to the same system colour as its own background becomes an empty box.

This one is worth dwelling on: an automated accessibility scan reported zero violations on a page where every primary action was unreadable. The computed values looked correct — white on dark blue. The failure was a rendering effect, because browsers paint a backplate behind text over a background image, and the label was the same colour as the plate.

Filled controls need an explicit system-colour pair. Scans cannot see this. Look at it.

Target sizes, at the widest viewport

Check pointer target sizes on desktop, not only on mobile. It is common to add a comfortable tap target inside a small-screen media query and leave the wide layout at the natural line height of the text. Navigation links seventeen pixels tall pass every automated check and fail the minimum target size.

Reflow, zoom, and text spacing

Three separate checks that people collapse into one:

  • narrow reflow at 320 CSS pixels, with no sideways scrolling;
  • a 400 per cent zoom equivalent, which is a 320 by 256 viewport;
  • increased line height, letter spacing, word spacing, and paragraph spacing applied as user styles.

Fluid spacing that looks elegant can still push a container past the viewport at one of the three.

Reduced motion at the token, not the component

If durations come from a custom property, override the property once under the reduced-motion query and every transition inherits it. Scattering per-component overrides guarantees one gets missed. Verify by reading a computed transition-duration with the preference active, not by reading the stylesheet.

Discovery signals that agree with each other

Your indexing directives, your robots.txt, and your sitemap should be three views of one decision. Common contradictions:

  • a sitemap listing a URL whose page answers noindex;
  • robots.txt advertising a sitemap that declares no URLs;
  • a canonical URL on a page you asked search engines to drop;
  • disallowing a URL in robots.txt that carries a noindex directive, so the crawler can never read the directive that would remove it.

Derive all three from one list. If they can disagree, eventually they will.

Alternates that resolve

If you publish more than one language, check that every declared language alternate returns 200. A common shape: content exists in one language, the alternates are generated from the full set of supported languages, and the site advertises addresses that answer 404.

The rule that avoids it: an alternate is a published sibling, not a supported locale.

Preview deployments that cannot compete

A preview deployment with production canonical URLs competes with production in search results. Gate discovery output on an explicit production signal, and check that a preview emits no canonical, no alternates, and an empty or absent sitemap.

Validators that report instead of throwing

Two failure modes worth testing directly.

First, dates. new Date("2026-02-31") does not fail — it rolls into March. A format check plus a parse check still admits dates that do not exist. Round-trip the value instead.

Second, order of operations. A refinement that calls toISOString on an invalid date throws, and a validator that throws reports nothing at all: the caller sees a crash rather than a field-level message. Guard validity first.

The artifact you ship, not the repository you build

If you distribute a sanitized copy of your source, verify the copy. A repository can be perfectly healthy while the artifact derived from it is broken: files that survive removal while importing modules that did not, helpers left with no consumer, generated files whose trailing byte differs from what the compiler emits.

Build the artifact, install it, and run its own verification command. Three separate customer-breaking defects reached a release before this check existed.

Tests that can actually fail

Before trusting a new check, break the thing it protects and watch it fail.

A forced-colors test that applies the preference after rendering can pass against ordinary colours and prove nothing. A test asserting a class name rather than a computed style passes for a focus ring that a later rule overrides. Both were in this list until someone tried to break them.

If a check has never been observed failing, it is a comment, not a test.