Skip to content

fix(build): clean stale vinext build output before rebuilds#1586

Open
NathanDrake2406 wants to merge 1 commit into
cloudflare:mainfrom
NathanDrake2406:nathan/clean-build-output
Open

fix(build): clean stale vinext build output before rebuilds#1586
NathanDrake2406 wants to merge 1 commit into
cloudflare:mainfrom
NathanDrake2406:nathan/clean-build-output

Conversation

@NathanDrake2406
Copy link
Copy Markdown
Contributor

Overview

Item Details
Goal Stop vinext build from retaining stale hashed server chunks across rebuilds
Core change Clean dist/ once before the production Vite build starts, with build.emptyOutDir: false preserved as the opt-out
Main boundary Build output ownership belongs to the CLI before the multi-environment builder and hybrid Pages pass write into sibling output dirs
Primary files packages/vinext/src/cli.ts, packages/vinext/src/build/clean-output.ts, tests/clean-build-output.test.ts
Expected impact Worker deploys no longer upload historical JS/MJS modules matched by Wrangler no_bundle rules

Fixes #1584.

Why

Production build output should represent the current build, not a historical superset of every hashed chunk ever emitted. This matters more for Cloudflare Workers because vinext's generated Wrangler config uses no_bundle: true plus ES module globs, so stale JS/MJS files still count toward upload size even when the current manifest no longer references them.

Area Principle / invariant What this PR changes
Build output A production build owns its output directory unless the user explicitly opts out vinext build removes dist/ before starting the Vite builder
Vite compatibility build.emptyOutDir: false should remain an escape hatch The CLI reads the raw Vite config and skips the upfront clean when that flag is explicitly false
Workers deploys Wrangler uploads every module matched by rules.globs in no_bundle mode Historical hashed modules are removed before Wrangler can see them

What changed

Scenario Before After
Default repeated vinext build Stale dist/server/**/{*.js,*.mjs} files could remain forever dist/ is cleaned once immediately before the build writes current artifacts
Hybrid App+Pages build The extra Pages Router server build still needs emptyOutDir: false to preserve RSC artifacts That pass stays unchanged because the one clean happens before all build phases
User sets build.emptyOutDir: false No explicit vinext behaviour The existing output is preserved
Out dir outside project root Risky to delete by default The helper follows Vite's default shape and skips outside-root output unless explicitly enabled
Maintainer review path
  1. packages/vinext/src/build/clean-output.ts for the cleanup decision and filesystem mutation boundary.
  2. packages/vinext/src/cli.ts for where the clean runs relative to standalone preflight, React upgrade, and createBuilder().buildApp().
  3. tests/clean-build-output.test.ts for default cleanup, explicit preservation, and outside-root safety.
  4. packages/vinext/src/build/run-prerender.ts for the updated comment describing the new cleanup owner.
Validation
  • vp test run --project unit tests/clean-build-output.test.ts
  • vp check
  • pnpm exec knip --no-progress
  • vp run vinext#build
  • Temp hybrid App+Pages Cloudflare runtime probe:
    • default rebuild removed dist/server/ssr/_next/static/stale-dead-module.js
    • changing a client component left counter_chunks_before=1 and counter_chunks_after=1
    • build.emptyOutDir: false preserved dist/server/ssr/_next/static/preserved-by-empty-out-dir-false.js
Risk / compatibility
  • Public API: no public API change.
  • Config: preserves Vite's build.emptyOutDir: false opt-out.
  • Build output: removes stale dist/ contents by default, matching the expected clean build model.
  • Workers: reduces accidental upload size when Wrangler deploys no_bundle module outputs.
  • Existing-app risk: apps relying on arbitrary persistent files under dist/ need to opt out with build.emptyOutDir: false.
Non-goals
  • Does not change the hybrid Pages Router server build's internal emptyOutDir: false, since that still preserves RSC artifacts from the same build.
  • Does not introduce a new vinext CLI flag for preserving build output.
  • Does not change Wrangler's generated no_bundle module rules.

References

Reference Why it matters
Issue #1584 Reports stale hashed chunks accumulating and being uploaded by Wrangler
Next.js clean distDir test Confirms Next.js expects the production build directory to be cleaned by default
Next.js build cleanup implementation Shows cleanDistDir cleanup before build work proceeds
Vite build.emptyOutDir docs Defines the default cleanup behaviour and explicit opt-out

Repeated vinext builds currently leave stale hashed server chunks in dist/server. Workers deployments with no_bundle upload every matched JS module, so stale chunks count against Worker size limits.

The CLI assumed Vite's per-environment emptyOutDir would remove old output, but the multi-environment build and hybrid Pages Router pass preserve dist/server artifacts.

Clean the vinext dist output once immediately before the Vite build starts, while preserving build.emptyOutDir: false as the escape hatch. Add focused tests for default cleaning, explicit preservation, and outside-root safety.
@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 24, 2026

Open in StackBlitz

npm i https://pkg.pr.new/vinext@1586

commit: 21bfeb9

@NathanDrake2406 NathanDrake2406 marked this pull request as ready for review May 24, 2026 10:11
Copilot AI review requested due to automatic review settings May 24, 2026 10:11
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR ensures vinext build does not retain stale hashed server chunks across rebuilds by introducing a one-time cleanup of the build output directory (dist/) before the production Vite build begins, while preserving build.emptyOutDir: false as an explicit opt-out.

Changes:

  • Added a cleanBuildOutput helper that safely removes the build output directory (defaulting to dist/) only when appropriate.
  • Wired the cleanup into the vinext build CLI flow, honoring a user’s explicit build.emptyOutDir: false from their Vite config as an escape hatch.
  • Added unit tests covering default cleanup behavior, opt-out preservation, and outside-root safety; updated prerender comment to reflect the new cleanup ownership.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated no comments.

File Description
tests/clean-build-output.test.ts Adds unit tests validating default cleanup, opt-out via emptyOutDir: false, and outside-root safety behavior.
packages/vinext/src/cli.ts Runs the cleanup once before the multi-environment build, and reads user build.emptyOutDir from Vite config to support opt-out.
packages/vinext/src/build/run-prerender.ts Updates comment to reflect that cleanup is now owned by vinext’s build flow rather than Vite’s emptyOutDir.
packages/vinext/src/build/clean-output.ts Introduces the cleanup implementation with a safety check to avoid deleting outside-root output by default.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

vinext build accumulates stale hashed chunks in dist/server/; wrangler then ships every historical build

2 participants