How to Fix 'No Matching Export' Errors with Shared Workers in Vite 8
Vite 8 changed how worker imports work. The ?sharedworker and ?worker query suffixes no longer produce a default export that wraps the new SharedWorker() constructor for you. Instead, Vite 8 exports only the resolved URL of the worker file, and you instantiate the worker yourself. If you're seeing No matching export in "worker.js?sharedworker" or SyntaxError: The requested module does not provide an export named 'default', this is the cause.
The Vite 7 Pattern That Breaks in Vite 8
In Vite 7 and earlier, the ?sharedworker suffix caused Vite to return a constructor-wrapping default export. Your code likely looked like this.
// Vite 7 style — this breaks in Vite 8.
import SharedWorkerWrapper from './analytics.worker.js?sharedworker';
// The default export was a class wrapping new SharedWorker().
const worker = new SharedWorkerWrapper();
The Vite 8 Fix: Import the URL and Instantiate Manually
Vite 8 aligns worker handling with the standard import.meta pattern. The ?sharedworker suffix now exports the resolved URL string only. You pass that URL to the native SharedWorker constructor yourself. Here's the correct migration.
// Import the resolved worker URL from Vite 8.
import analyticsWorkerUrl from './analytics.worker.js?sharedworker&url';
// Instantiate the native SharedWorker with the URL.
const worker = new SharedWorker(analyticsWorkerUrl, {
type: 'module',
name: 'analytics',
});
Alternative: Use import.meta.url with new URL()
If you prefer to avoid query suffixes altogether, Vite 8 fully supports the new URL(..., import.meta.url) pattern. This approach is framework-agnostic and works in other bundlers like webpack 5 and Rollup, which makes your code more portable.
// Build a resolved URL using the standard pattern.
const workerUrl = new URL(
'./analytics.worker.js',
import.meta.url
);
// Pass it to SharedWorker directly.
const worker = new SharedWorker(workerUrl, {
type: 'module',
name: 'analytics',
});
Regular Workers Have the Same Breaking Change
This breaking change isn't limited to SharedWorker. Dedicated workers imported with ?worker follow the same pattern shift. If you have both types in your codebase, fix them all at once.
// Dedicated worker — Vite 8 style.
import heavyTaskUrl from './heavy-task.worker.js?worker&url';
const dedicatedWorker = new Worker(heavyTaskUrl, {
type: 'module',
});
Gotchas and Edge Cases
The type: 'module' option is required. Vite outputs ES modules for workers in dev mode. If you omit this option, the browser parses the worker script as a classic script and produces cryptic import statement syntax errors in the worker console, not in your main thread. Always set type: 'module'.
The &url suffix matters. Using ?sharedworker without &url might still trigger bundling behavior in some Vite 8 pre-release versions. Be explicit with ?sharedworker&url to guarantee that you receive a string URL. If you're on a stable Vite 8 release and ?sharedworker alone returns a URL string, &url is technically redundant, but it clarifies your intent and costs nothing.
TypeScript users need updated type declarations. Vite ships updated client.d.ts types, but if you pinned an older vite/client reference or use a custom *.worker.js module declaration, you need to update it.
// Update your custom type declaration in env.d.ts.
declare module '*?sharedworker&url' {
const workerUrl: string;
export default workerUrl;
}
declare module '*?worker&url' {
const workerUrl: string;
export default workerUrl;
}
Which Approach Should You Pick?
Use the new URL(..., import.meta.url) pattern. It's the standards-track approach, works across bundlers, and doesn't depend on Vite-specific query suffixes that might change again in future major versions. The ?sharedworker&url suffix works well if you need Vite to apply worker-specific plugins or transforms, but for most codebases the new URL pattern is more portable and easier to reason about.
If you have many worker imports scattered across a large codebase, a quick codemod with jscodeshift or a project-wide find-and-replace targeting the old import ... from '...?sharedworker' pattern handles the migration in minutes.