Skip to content

add docs for Vercel deploy CACHE_KEY mismatch between entry JS and preload files when using worker threads #672

@taehee-ms

Description

@taehee-ms

Summary

When building with deploy: 'vercel', the preload/loader JS files are generated with different CACHE_KEY values than the client entry JS bundle. This causes some client-side navigations to fail on Vercel production because the browser requests preload files that don't exist, receives HTML instead, and rejects them due to MIME type mismatch.

Environment

  • one version: 1.4.11
  • Deploy target: Vercel (web.deploy: 'vercel')
  • Build tool: bun
  • OS: macOS (local), Linux (Vercel CI)

Reproduction

  1. Create a project with one using deploy: 'vercel' and at least one SSG route
  2. Run bun run build
  3. Inspect the generated .vercel/output/static/assets/ directory:
# Entry JS has one CACHE_KEY:
grep -oE 'ch="[0-9]+"' .vercel/output/static/assets/_virtual_one-entry-*.js
# => ch="31971176"

# Preload files have DIFFERENT CACHE_KEYs:
ls .vercel/output/static/assets/ | grep '_preload\.' | grep -oE '_[0-9]+_preload' | sort -u
# => _3375520_preload
# => _7325102_preload
# => _28556756_preload
# => _32467716_preload
# => ... (none match 31971176)
  1. Deploy to Vercel — clicking some internal link results in a white blank page

Symptoms on Vercel Production

Browser console shows:

Failed to load module script: Expected a JavaScript-or-Wasm module script but the server
responded with a MIME type of "text/html". Strict MIME type checking is enforced for module
scripts per HTML spec.

[one] preload error for /ko/privacy-policy: TypeError: Failed to fetch dynamically imported
module: https://example.com/assets/ko_privacy-policy_5481575_preload.js

All _preload.js, _preload_css.js, and _vxrn_loader.js files return text/html because they don't exist on the CDN — the actual files have different numeric keys in their names.

Root Cause

In constants.ts:

export const CACHE_KEY = `${process.env.ONE_CACHE_KEY ?? Math.round(Math.random() * 100_000_000)}`

The build uses worker threads by default for parallel page building (see workerPool.ts / buildPageWorker.ts). Each worker thread imports constants.ts independently and generates its own random CACHE_KEY via Math.random().

Meanwhile, the main Vite build process compiles the client entry JS with the main process's CACHE_KEY (via Vite define in one.ts):

'process.env.ONE_CACHE_KEY': JSON.stringify(CACHE_KEY),

This define only affects the client bundle (compile-time replacement). It does not set the actual process.env.ONE_CACHE_KEY environment variable, so worker threads never see it.

Result

Component CACHE_KEY Source
Client entry JS (browser) A Main process Math.random(), inlined by Vite define
Worker 1 (preload files) B Worker's own Math.random()
Worker 2 (preload files) C Worker's own Math.random()
Worker N... N Each worker's own Math.random()

The client requests /assets/ko_privacy-policy_A_preload.js but the file on disk is /assets/ko_privacy-policy_B_preload.js.

Why it works with bun run serve but not Vercel

In oneServe.ts, the serve middleware has a graceful fallback for missing preload files:

if (c.req.path.endsWith(PRELOAD_JS_POSTFIX)) {
  if (!preloads[c.req.path]) {
    c.header('Content-Type', 'text/javascript')
    c.status(200)
    return c.body(``)  // empty JS — no error
  }
}

On Vercel, there's no such middleware — the missing file falls through to the catch-all rewrite route in config.json, which rewrites to / and returns the root HTML page. The browser then rejects HTML as a JavaScript module.

Suggested Fix

Set process.env.ONE_CACHE_KEY in the main process before spawning worker threads, so all workers inherit the same value:

In build.ts, before the worker pool is initialized:

// Ensure all workers share the same CACHE_KEY
process.env.ONE_CACHE_KEY = CACHE_KEY

Or alternatively, pass CACHE_KEY to workers via postMessage and have buildPageWorker.ts set process.env.ONE_CACHE_KEY before importing constants.ts.

Workaround

Set the ONE_CACHE_KEY environment variable explicitly before building:

ONE_CACHE_KEY=my_stable_key bun run build

For Vercel, in vercel.json:

{
  "buildCommand": "ONE_CACHE_KEY=$VERCEL_GIT_COMMIT_SHA bun run build"
}

This ensures all processes and worker threads use the same cache key.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions