← ./articles

Vite Build Crashed After Emit? Check Fresh Output Before Failing the Build

On Windows, a Vite or Node.js build can sometimes crash after it has already emitted a valid dist/ directory.

That creates an awkward question: did the build fail, or did the tool crash during teardown after producing usable artifacts?

The wrong answer is to check only whether dist/ exists. A stale dist/ from a previous run can make a failed build look successful. The safer answer is to clean output first, run the build, then verify that the files were generated fresh in this run.

The failure pattern

The symptom often looks like this:

vite build
✓ built in 4.2s
process exited with code -1073741819

or a Windows access violation such as:

0xC0000005

The build log says files were built, but the process exits non-zero. If you are packaging a Tauri app or running CI, a strict non-zero exit code stops the release.

Sometimes that is correct. Sometimes the generated output is complete and fresh, and only the teardown path crashed.

Why dist exists is not enough

This check is unsafe:

vite build
test -f dist/index.html

If dist/index.html was created yesterday, the check still passes. You may ship stale files from a previous build.

A reliable check must prove:

  • dist/ was cleaned before this run
  • index.html was created during this run
  • referenced asset files exist
  • required static assets, such as WASM files, are present and non-empty

PowerShell wrapper pattern

PowerShell works well as a parent process on Windows because a Node child crash does not usually kill the PowerShell wrapper.

$ErrorActionPreference = "Stop"
$Dist = Join-Path (Get-Location) "dist"

if (Test-Path $Dist) {
  Remove-Item $Dist -Recurse -Force
}

npm run typecheck
npm run vite-build

$index = Join-Path $Dist "index.html"
if (-not (Test-Path $index)) {
  throw "dist/index.html was not created"
}

$age = (Get-Date) - (Get-Item $index).LastWriteTime
if ($age.TotalSeconds -gt 10) {
  throw "dist/index.html is stale"
}

For a production wrapper, capture the Vite exit code and then decide whether to accept a post-emit crash only if the freshness checks pass.

Retry only when the output is incomplete

If the process exits non-zero but output is fresh and complete, you may classify it as a post-emit crash. If output is missing or stale, retry.

exit 0 + fresh output        -> success
exit non-zero + fresh output -> post-emit crash, optionally accept
exit non-zero + stale output -> real failed attempt, retry or fail

The important part is not to hide every non-zero exit code. TypeScript errors, missing imports, and incomplete assets should still fail the build.

When this applies

This pattern is useful when:

  • the build log reports success but the process exits non-zero
  • the crash is intermittent
  • the crash appears on Windows during process cleanup
  • packaging depends on generated frontend files
  • you cannot trust a stale dist/ directory

It is especially useful in desktop app pipelines where a frontend build is followed by Tauri or another native packaging step.

Summary

Do not treat "dist exists" as proof of a successful Vite build.

Clean dist/, run the build, and verify fresh output. If the build crashes after emit, your wrapper can make a deliberate decision instead of confusing stale artifacts with success.

References