Summary
The standard bundler-friendly Web Worker pattern recommended by Next.js/webpack/Vite:
const worker = new Worker(new URL('../echo.worker.ts', import.meta.url), { type: 'module' })
does not work under vinext, in either vinext dev or vinext build. The worker script itself is bundled and emitted correctly, but the URL base is broken: vinext's import-meta-url plugin replaces client-side import.meta.url with the deterministic literal file:///ROOT/<relative path> (dist/plugins/import-meta-url.js, importMetaUrlValue()), so the resolved worker URL has a file:// origin and the browser rejects the construction with a SecurityError (Failed to construct 'Worker': Script at 'file:///...' cannot be accessed from origin 'http://localhost:...').
Reproduction
Minimal App Router project (5 files):
package.json
{
"name": "vinext-worker-repro",
"private": true,
"type": "module",
"scripts": { "dev": "vinext dev", "build": "vinext build" },
"dependencies": { "next": "16.1.6", "react": "^19.2.6", "react-dom": "^19.2.6" },
"devDependencies": { "@vitejs/plugin-rsc": "^0.5.27", "vinext": "0.2.1", "vite": "^8.0.16" }
}
vite.config.ts
import vinext from "vinext";
import { defineConfig } from "vite";
export default defineConfig({ plugins: [vinext()] });
src/app/layout.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
src/app/page.tsx
'use client'
import { useState } from 'react'
export default function Home() {
const [result, setResult] = useState('(not run)')
function run() {
try {
const worker = new Worker(new URL('../echo.worker.ts', import.meta.url), {
type: 'module',
})
worker.onmessage = (e) => setResult(`worker replied: ${e.data}`)
worker.postMessage('ping')
} catch (err) {
setResult(`error: ${err}`)
}
}
return (
<main>
<button onClick={run}>start worker</button>
<p>{result}</p>
</main>
)
}
src/echo.worker.ts
self.onmessage = (e: MessageEvent<string>) => {
self.postMessage(`echo: ${e.data}`)
}
Observed output
vinext dev — the served transform of src/app/page.tsx contains:
new Worker(new URL(/* @vite-ignore */ "/src/echo.worker.ts?worker_file&type=module", '' + "file:///ROOT/src/app/page.tsx"), { type: "module" })
vinext build — the client chunk in dist/ contains:
new Worker(new URL(`/_next/static/echo.worker-C7Yrb_A6.js`,`file:///ROOT/src/app/page.tsx`), ...)
In both cases Vite's worker plugin correctly rewrites the first argument to the served/emitted script path, but the base (import.meta.url) has already been replaced with the file:///ROOT/... literal, so the final URL resolves to a file:// origin and worker construction throws.
Expected
The worker URL should resolve against the page origin (e.g. rewrite these new URL bases to self.location.href, or skip the import.meta.url replacement when it is used as a new URL base that Vite's worker/asset plugins rewrite).
Environment
- vinext 0.2.1 (also reproduced with 1.0.0-beta.1)
- vite 8.1.4, next 16.1.6, @vitejs/plugin-rsc 0.5.27
- Node v22.16.0, Windows 11 (the replacement is platform-independent, so this should reproduce on any OS)
Summary
The standard bundler-friendly Web Worker pattern recommended by Next.js/webpack/Vite:
does not work under vinext, in either
vinext devorvinext build. The worker script itself is bundled and emitted correctly, but theURLbase is broken: vinext'simport-meta-urlplugin replaces client-sideimport.meta.urlwith the deterministic literalfile:///ROOT/<relative path>(dist/plugins/import-meta-url.js,importMetaUrlValue()), so the resolved worker URL has afile://origin and the browser rejects the construction with a SecurityError (Failed to construct 'Worker': Script at 'file:///...' cannot be accessed from origin 'http://localhost:...').Reproduction
Minimal App Router project (5 files):
package.json
{ "name": "vinext-worker-repro", "private": true, "type": "module", "scripts": { "dev": "vinext dev", "build": "vinext build" }, "dependencies": { "next": "16.1.6", "react": "^19.2.6", "react-dom": "^19.2.6" }, "devDependencies": { "@vitejs/plugin-rsc": "^0.5.27", "vinext": "0.2.1", "vite": "^8.0.16" } }vite.config.ts
src/app/layout.tsx
src/app/page.tsx
src/echo.worker.ts
Observed output
vinext dev— the served transform ofsrc/app/page.tsxcontains:vinext build— the client chunk indist/contains:In both cases Vite's worker plugin correctly rewrites the first argument to the served/emitted script path, but the base (
import.meta.url) has already been replaced with thefile:///ROOT/...literal, so the final URL resolves to afile://origin and worker construction throws.Expected
The worker URL should resolve against the page origin (e.g. rewrite these
new URLbases toself.location.href, or skip theimport.meta.urlreplacement when it is used as anew URLbase that Vite's worker/asset plugins rewrite).Environment