Skip to content

Published audit

Eleven findings.
None of them were bugs.

Most people selling an audit describe the deliverable. This is one, in full, so you can judge the work before you pay for it rather than after.

Subject
A production Next.js 16 application, generated end to end by an AI coding agent.
Stack
Next.js 16 · React 19 · TypeScript · Tailwind 4 · Prisma · Vercel
Size
118 files, ~16,000 lines
Brief
Reviewed before it was pointed at real traffic.
Scope
Findings below are the complete set, not a selection.

Findings by severity

  • critical2
  • high2
  • medium5
  • low2
  1. F-01criticalproxy.ts (repository root)

    The security middleware was never running

    What was there

    The application shipped a strict Content-Security-Policy with a per-request nonce, built in a well-tested module and wired up in a middleware file at the repository root. With the app directory under src/, Next.js expects the middleware beside it. At the root it is silently ignored.

    What it would have cost

    No CSP header was sent. No nonce was generated. Every protection the codebase was written to provide — the nonce, strict-dynamic, the frame-ancestors lockout — was absent in production, while the module implementing it sat there passing its own unit tests.

    Why nothing caught it

    Nothing fails. There is no warning at build time, no error at runtime, and the page renders identically. It is only visible by inspecting response headers on a served production build — which is not something a generated test suite does.

    Fix

    Moved to src/proxy.ts. Confirmed by "Proxy (Middleware)" appearing in the build output and a rotating nonce in the live response. A regression test now asserts both the file location and that the nonce differs between requests.

  2. F-02criticalsrc/app/robots.ts

    The site told every search engine not to index it

    What was there

    robots.txt was fail-closed behind an environment variable, SEO_INDEXABLE, which had to be explicitly set to true for the site to be crawlable. That variable existed only on the platform the code was originally generated for.

    What it would have cost

    On any other host the production site served "Disallow: /" to every crawler. A marketing site whose entire purpose is being found was invisible to search, and to the AI crawlers that increasingly answer "who should I hire for this".

    Why nothing caught it

    Fail-closed is the correct default for preview deploys, and the code was right to do it. What was missing is that the flag was tied to one specific host. The failure only appears after the platform changes, which is exactly when nobody re-reads robots.txt.

    Fix

    Indexing is on by default; preview environments are excluded by the host's own environment signal rather than a hand-set flag. Verified against the live response.

  3. F-03highsrc/lib/env.ts, prisma/schema, package.json

    A required database that nothing used, and that broke the build

    What was there

    DATABASE_URL was declared a required URL in build-time environment validation, and prisma generate ran on every install. The Prisma schema contained zero models. The only reference to the client anywhere in the codebase was a commented-out import.

    What it would have cost

    The build fails outright on any host without a Postgres attached. A database was being provisioned, migrated and paid for on every deploy to serve no queries at all.

    Why nothing caught it

    The generator scaffolded a full data layer because the template had one, then built a site that never needed it. Nothing prunes the parts of a scaffold you did not use, and a required env var is invisible until you deploy somewhere that lacks it.

    Fix

    Prisma, the schema, the client singleton and the required env var removed. Build verified with no database present.

  4. F-04highFour files, including the main landing page and the nav

    The primary call to action pointed at a mailbox that did not exist

    What was there

    A placeholder contact address on a platform-owned domain was hardcoded in four separate places rather than referenced from one.

    What it would have cost

    Every "get in touch" link on the live site went to an address nobody reads. The duplication also guaranteed that fixing it in one place would leave the other three wrong.

    Why nothing caught it

    Generated code repeats a constant wherever it is needed, because each file is written in isolation. There is no pressure toward a single source until a human notices the same string four times.

    Fix

    One contact module. A test now fails the build if an email address, phone number or profile URL appears anywhere else in the source tree.

  5. F-05medium/example, /api/example

    Scaffolding left publicly crawlable

    What was there

    A demonstration page and its API route were live, reachable and indexable. The page's own header comment read: "Copy the shape for a real resource, then delete this page."

    What it would have cost

    Template scaffolding served on the production domain, discoverable by anyone who guessed the path or followed a crawler.

    Why nothing caught it

    The instruction to delete it was written into a comment rather than enforced anywhere. Comments do not fail builds.

    Fix

    Removed, along with its contract module and test. A test now fails on any placeholder string reaching a rendered page.

  6. F-06mediumHero and closing sections

    Dark mode silently collapsed

    What was there

    Both sections hardcoded a near-black background to invert against the page. In dark mode the page background was almost exactly the same value, so the two surfaces became indistinguishable.

    What it would have cost

    The entire sectioning device that gave the page its rhythm disappeared for every visitor whose system is set to dark, which on a developer-facing site is most of them.

    Why nothing caught it

    The colour was hardcoded rather than derived from a token, so it could not respond to the theme. In light mode it looks deliberate and correct, which is where it was reviewed.

    Fix

    Inverted sections are theme-aware: a dark slab in light mode, a raised surface in dark mode, so the contrast survives in both.

  7. F-07mediumInteractive diagram component

    An accessibility pattern that was worse than not having one

    What was there

    A control group declared role="tablist" and role="tab" with no tabpanel, no aria-controls, no roving tabindex and no arrow-key handling. Selection was additionally bound to mouse hover.

    What it would have cost

    A screen reader is told there is a tab interface and then finds none of the behaviour that implies. Incomplete ARIA is worse than no ARIA: it makes a promise the markup does not keep. Hover-binding also made the control unusable on touch.

    Why nothing caught it

    The ARIA attributes look like diligence and read as correct in review. Only the missing half — focus management and the panel relationship — reveals the problem, and nothing in a build or a test suite checks for it.

    Fix

    Reduced to honest toggle buttons with aria-pressed, hover selection removed. Tests assert the partial pattern cannot return.

  8. F-08mediumTwo interactive components

    Invalid HTML in two components

    What was there

    A heading element nested inside a button — flow content inside a phrasing-content model — and collapsed detail hidden with a zero max-height rather than the hidden attribute.

    What it would have cost

    Invalid nesting is handled inconsistently across browsers and assistive technology. The max-height approach leaves visually hidden content in the accessibility tree, so a screen reader reads collapsed panels aloud.

    Why nothing caught it

    Both render correctly. Neither TypeScript nor the test suite validates HTML content models.

    Fix

    Heading moved outside the control; collapsed content uses hidden.

  9. F-09mediumShared eyebrow label style

    Text below the accessibility contrast floor

    What was there

    A recurring label style used a mid-tone brand colour measuring below 4.5:1 against the light background.

    What it would have cost

    A WCAG AA failure repeated on every page that used the style.

    Why nothing caught it

    The colour was picked from a generated ramp and looks fine. Contrast is a measurement, not a judgment, and nothing was measuring.

    Fix

    Recoloured to clear AA in both themes, confirmed by automated audit.

  10. F-10lowBooking route

    A third-party embed loaded for everyone, and rendered clipped

    What was there

    A full third-party scheduling application was loaded eagerly on page load, inside a fixed aspect-ratio container that cropped its own layout.

    What it would have cost

    Every visitor paid the download and execution cost whether or not they intended to book, and those who did book saw a calendar with its header and time-slot column cut off.

    Why nothing caught it

    The container was sized to look right in one viewport. Third-party embeds manage their own internal layout and do not respect an imposed aspect ratio.

    Fix

    Loads on request behind a lightweight facade, at full width and its own height.

  11. F-11lowGlobal stylesheet

    Animation with no reduced-motion escape

    What was there

    Two decorative animations ran indefinitely with no prefers-reduced-motion handling.

    What it would have cost

    An accessibility problem for motion-sensitive visitors, and a continuous battery cost on mobile.

    Why nothing caught it

    Motion preferences are invisible on the machine writing the code.

    Fix

    All decorative motion stops when reduced motion is requested.

The pattern underneath all eleven

Not one of these is a bug. Every single file does what it was asked to do, and the application ran correctly the entire time.

They are gaps between correct pieces. Middleware that works, in a location nothing loads. A safety default tied to a platform that changed. A database wired up for a product that never queried it. A contact address duplicated because each file was written alone.

That is the consistent shape of generated code, and it is why reviewing it is a different job from reviewing code someone wrote by hand. A person who writes the whole system carries a model of how the parts meet. Generated code has no such model — every file is locally correct and nothing owns the seams.

Finding those seams is what the audit is.

Outcome

Every finding above is fixed and the fixes are in public version control.

Accessibility, best practices and SEO now score 100 on automated audit across every page, on mobile and desktop. The logic layer carries 100% test coverage, with regression tests written specifically for the two findings that were invisible in local development.

What this does not prove

This codebase has no authentication, no database and no user data. The highest-severity categories an audit usually turns up — a route that authenticates without authorising, an unbounded per-user cost, an N+1 that only appears at scale — were not present here because there was nothing for them to be present in. This report demonstrates the method, not the full range of what it finds.

The subject is anonymised, as a client's codebase would be. What it is not is a paid engagement dressed up as one: it is a worked example, published because a report you can read in full is worth more than a description of one you cannot.

Eleven findings is what a week of reading turned up in roughly sixteen thousand lines. A larger system with real users and real money moving through it takes longer and finds worse.

This is what lands in your inbox.

Ranked by what actually breaks first, with a live hour to argue about it.