cloudflare / cloudflare/vinext
Windows: production server 404s every nested static asset - StaticFileCache keys built with backslashes
- Dominant language
- TypeScript
- Stars
- 8.8k
- Forks
- 406
- Avg merge
- 2d 6h
- Merged PRs (30d)
- 120
Description
# Upstream issue draft — for Rob's word before filing (external publish)
Target: vinext repository (npm package `vinext`; repo URL per its package.json/npm page)
Verified present in: 0.0.50 and 1.0.0-beta.3 (latest at time of writing, 2026-07-24)
---
**Title:** Windows: production server 404s every nested static asset — StaticFileCache keys built with backslashes
**Body:**
On Windows, `vinext start` (production server) returns the router's NOT_FOUND for every static file in a subdirectory of `dist/client/` — including all hashed `/assets/*` JS/CSS — while SSR routes work fine. The page loads as unstyled HTML with a failed dynamic import.
**Cause:** `src/server/static-file-cache.ts` (`walkFilesWithStats`) yields `relativePath: path.relative(base, file)`. On Windows `path.relative` returns backslash-separated paths, so cache keys become `/assets\index-XXXX.js` while request lookups use URL pathnames (`/assets/index-XXXX.js`). Every nested file misses. Because a provided `StaticFileCache` has no filesystem fallback in `tryServeStatic` (a miss returns `false` immediately), the request falls through to routing and 404s. The `.vite/` exclusion and `assets/`-prefix immutable-caching checks silently misbehave for the same reason (`startsWith("assets/")` never matches `assets\...`).
**Repro (Windows):**
```
npx vinext build
npx vinext start --port 3000
# open the site: every /assets/* request → 404 JSON {"state":"NOT_FOUND"}
```
**Fix (one line):**
```diff
- relativePath: path.relative(base, batch[j]),
+ relativePath: path.relative(base, batch[j]).split(path.sep).join("/"),
```
Verified locally on Windows 11 / Node 24: all nested lookups resolve, `.vite/` exclusion and hashed-asset cache headers behave correctly, page loads with zero console errors.
Present in 0.0.50 through 1.0.0-beta.3 (the beta's `walkFilesWithStats` is identical in this respect).
---
BOUNDARY: NOT FILED. Filing is an external publish under RIG001 law — Rob's word required. On word: file via gh or web, record issue URL on the rail.
Contributor guide
Research direction
Start in src/server/static-file-cache.ts at walkFilesWithStats and trace how its relativePath values reach tryServeStatic. Reproduce with npx vinext build and npx vinext start on Windows; done means nested /assets/* requests resolve, .vite/ is excluded, and hashed assets receive the expected cache behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100