Back to Blog

Systematic Cross-Tool Defect Auditing and Next.js 15 Middleware Hardening

By · 8 min read
Next.js Architecture Code Audit Software Quality TypeScript

When a product suite grows through sibling tools, defects rarely stay isolated. A bug fixed in one app is often a search query away from another copy in a neighboring app: same route pattern, same provider wrapper, same picker component, same stale state assumption. The August maintenance pass across the AI ad/video tools turned that observation into a deliberate audit workflow.

The anchor PR was #222 (fix/port-presenter-and-variant-multiplier-fixes) in the main video-generation service: fourteen defects ported from two sibling tools, audited rather than assumed. Minutes later, PR #223 (fix/upload-body-size-key-for-next-15) handled a separate framework gotcha where the upload body-size cap lived under a config key Next.js 15 ignored. Together they are a useful pattern: treat every root-cause fix as a prompt to audit the fleet, and treat every framework upgrade as a contract review, not a version bump.

Never fix a bug in isolation. Turn every root-cause fix into a cross-repository audit pattern for sibling codebases.

The Next.js 15 upload body-size gotcha

The upload issue was easy to misread because the code looked familiar. Legacy Next.js API-route config often used export const config = { api: { bodyParser: false } } or a nested body-size value. In App Router route handlers, especially after the Next.js 15 migration, that pattern can be ignored or simply not apply where engineers think it applies. The symptom appears elsewhere: 413 responses, truncated multipart bodies, or missing file fields.

PR #223 moved the upload path away from implicit parser behavior and toward explicit route-handler ownership of the stream. That same fix is the backbone of Streaming Master Uploads to Disk: accept the request as bytes, stream to an artifact, then validate the artifact.

// Suspicious in App Router upload handlers:
export const config = {
  api: {
    bodyParser: { sizeLimit: '100mb' },
  },
};

// Safer route-handler shape:
export const runtime = 'nodejs';
export const dynamic = 'force-dynamic';
export const maxDuration = 60;

export async function POST(req: Request) {
  const upload = await streamUploadToTempFile(req, 'master.mp4');
  try {
    await assertVideoProbePasses(upload.path);
    return Response.json(await ingestMasterVideo(upload.path));
  } finally {
    await upload.cleanup();
  }
}

The cross-tool audit protocol

The most important phrase in #222's title is "audited, not assumed." Porting fixes by memory is how teams create fake confidence. The protocol was stricter:

  1. Extract the root cause. Describe the bug as a reusable pattern: stale render input, unsafe paid action, wrong provider request shape, hidden body parser, picker overflow.
  2. Search sibling repos by behavior. Do not only search for one function name. Search equivalent routes, components, and state transitions.
  3. Classify each hit. Mark it present, absent, intentionally different, or untestable. "Not sure" is not a pass.
  4. Port only verified fixes. Submit atomic PRs that name the source defect and the target behavior.
  5. Update docs and changelogs. The next audit should start from a known map, not from chat memory.

This mirrors the discipline from Post-Launch Audit, but across repositories instead of one launch surface. The difference is that cross-tool audits need a translation step: a defect may not have the same filename in every app, but it often has the same user-facing shape.

Defect families that repeated

Defect family Example source Audit question
Paid-action ownership Hook Machine #53 Can any double-click or retry dispatch two paid provider calls?
Render input drift Hook Machine #57 Can resume/regenerate lose voice, product, or prompt locks?
Audio/render DOM leakage Hook Machine #58 Can preview-only DOM elements enter headless frame capture?
Upload body limits Video-generation #223 / Variant Multiplier #64/#66 Does this route own raw upload bytes or rely on hidden parser limits?
Vendor request shape Presenter #9, Hook Machine #49, Variant Multiplier #62 Can the real provider reject fields the local schema allowed?
Picker and catalog scaling Presenter #19 Do large voice/character/product catalogs break layout or search?

Porting fixes without cargo culting

A cross-tool fix should not blindly copy code. Hook Machine, Presenter Generation, Variant Multiplier, and the main video-generation service share concepts, but each app has different workflows. A regenerate action in Hook Machine is not the same as a scene redo in Presenter Generation or a section-swap reroll in Variant Multiplier. The audit needs to preserve the behavior that mattered, not the exact function body.

That distinction showed up in the paid-action work. In one tool, the risk was a regenerate double-click. In another, it was a button that looked dead while an action was still pending. In a third, it was ledger idempotency around retries. The shared invariant was server-owned action state. The implementation varied per tool.

type AuditFinding =
  | { status: 'present'; repo: string; fixBranch: string; test: string }
  | { status: 'absent'; repo: string; evidence: string }
  | { status: 'different_by_design'; repo: string; reason: string };

function requireEvidence(finding: AuditFinding) {
  if (finding.status === 'present' && !finding.test) {
    throw new Error('A ported defect needs a test or reproducible check');
  }
  return finding;
}

Framework upgrade hardening

The Next.js 15 body-size issue belongs in a broader checklist. Framework upgrades break assumptions in places that do not always fail at compile time: route config, runtime selection, cache behavior, request body parsing, streaming APIs, and server action boundaries. The fix is to audit by capability rather than by package version.

  • Large uploads: verify the route receives raw bytes and can stream past 10 MB.
  • Long jobs: verify route duration and background handoff do not depend on defaults.
  • Cache-sensitive state: mark dynamic routes explicitly when job state must be fresh.
  • Provider wrappers: test against real request shapes, not only local TypeScript types.

That is how a framework migration becomes reliability work instead of a dependency chore.

Audited PR summary

PR / branch Scope Resolution
#222 fix/port-presenter-and-variant-multiplier-fixes Fourteen verified defects ported into the main video-generation service Converted sibling-tool bug fixes into explicit checks and equivalent fixes
#223 fix/upload-body-size-key-for-next-15 Upload body-size config ignored after framework migration Moved large upload handling to explicit stream-owned route handlers
#60 fix/port-presenter-delivery-and-vendor-fixes Presenter delivery and vendor bugs with equivalent section-swap risks Ported only defects that reproduced in the target workflow
#45 port/presenter-hook-catch-and-wire-fixes Presenter fixes that were live bugs in Hook Machine too Applied equivalent hook-generation fixes and synced docs afterward

What I'd do differently

I would extract shared upload handlers, provider request validators, and paid-action guards into versioned packages sooner. Cross-repo audits are valuable, but shared libraries make the desired behavior harder to drift from in the first place. The audit still has a role: proving adoption and catching places where a workflow genuinely needs a different implementation.

I would also keep a living "defect families" document. Individual PRs are easy to close and forget. A defect family list turns each incident into future search terms, test fixtures, and review prompts.

The pattern: proactive multi-repo maintenance

Bug fixes should improve more than the file they touch. When tools share workflows, providers, and framework surfaces, every root cause is a candidate audit pattern. The engineering move is to preserve specificity — exact PRs, exact symptoms, exact checks — while raising the fix to the fleet level. That is how a maintenance sprint compounds into platform quality.

Related Articles