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
- Create a project with
one using deploy: 'vercel' and at least one SSG route
- Run
bun run build
- 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)
- 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.
Summary
When building with
deploy: 'vercel', the preload/loader JS files are generated with differentCACHE_KEYvalues 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
oneversion: 1.4.11web.deploy: 'vercel')Reproduction
oneusingdeploy: 'vercel'and at least one SSG routebun run build.vercel/output/static/assets/directory:Symptoms on Vercel Production
Browser console shows:
All
_preload.js,_preload_css.js, and_vxrn_loader.jsfiles returntext/htmlbecause they don't exist on the CDN — the actual files have different numeric keys in their names.Root Cause
In
constants.ts:The build uses worker threads by default for parallel page building (see
workerPool.ts/buildPageWorker.ts). Each worker thread importsconstants.tsindependently and generates its own randomCACHE_KEYviaMath.random().Meanwhile, the main Vite build process compiles the client entry JS with the main process's
CACHE_KEY(via Vitedefineinone.ts):This
defineonly affects the client bundle (compile-time replacement). It does not set the actualprocess.env.ONE_CACHE_KEYenvironment variable, so worker threads never see it.Result
AMath.random(), inlined by VitedefineBMath.random()CMath.random()NMath.random()The client requests
/assets/ko_privacy-policy_A_preload.jsbut the file on disk is/assets/ko_privacy-policy_B_preload.js.Why it works with
bun run servebut not VercelIn
oneServe.ts, the serve middleware has a graceful fallback for missing preload files: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_KEYin the main process before spawning worker threads, so all workers inherit the same value:In
build.ts, before the worker pool is initialized:Or alternatively, pass
CACHE_KEYto workers viapostMessageand havebuildPageWorker.tssetprocess.env.ONE_CACHE_KEYbefore importingconstants.ts.Workaround
Set the
ONE_CACHE_KEYenvironment variable explicitly before building: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.