dotnet/AspNetCore.Docs · error · Error
worker exports not loaded
Error message
worker exports not loaded
What it means
The Blazor web-worker script sets `startupError` if `dotnet.create()` / `getAssemblyExports` fails, then in the message handler throws `startupError || 'worker exports not loaded'` when `assemblyExports` is still falsy. The literal fallback fires when the worker never obtained .NET assembly exports and no specific startup error was captured.
Source
Thrown at aspnetcore/blazor/blazor-with-dotnet-on-web-workers.md:326
```javascript
import { dotnet } from '../_framework/dotnet.js';
let assemblyExports;
let startupError;
try {
const { getAssemblyExports, getConfig } = await dotnet.create();
const config = getConfig();
assemblyExports = await getAssemblyExports(config.mainAssemblyName);
} catch (err) {
startupError = err.message;
}
self.addEventListener('message', async e => {
try {
if (!assemblyExports) {
throw new Error(startupError || 'worker exports not loaded');
}
let result;
switch (e.data.command) {
case 'generateQR':
result = assemblyExports.QRGenerator.Generate(e.data.text, e.data.size);
break;
default:
throw new Error(`Unknown command: ${e.data.command}`);
}
self.postMessage({ command: 'response',
requestId: e.data.requestId, result });
} catch (err) {
self.postMessage({ command: 'response',
requestId: e.data.requestId, error: err.message });
}
});View on GitHub (pinned to c67a80103a)
Solutions
- Have the main thread wait for a ready signal from the worker (e.g. worker posts `{command:'ready'}` after `getAssemblyExports`) before sending commands.
- Ensure `Generate` is annotated `[JSExport]` and the project sets `<AllowLocation>true</AllowLocation>` / proper runtime assets so exports are discovered.
- Verify the published `mainAssemblyName` matches the assembly containing `QRGenerator`.
- Inspect `startupError` (post it back in the catch) to distinguish boot failure from a race.
Example fix
// before
worker.postMessage({ command: 'generateQR', text, size, requestId });
// after
await waitForWorkerReady(worker);
worker.postMessage({ command: 'generateQR', text, size, requestId }); Defensive patterns
Strategy: validation
Validate before calling
// Main thread: wait for worker ready before sending commands
function postWhenReady(worker, msg) {
return new Promise((resolve, reject) => {
const onReady = (e) => {
if (e.data.command === 'ready') {
worker.removeEventListener('message', onReady);
worker.postMessage(msg);
resolve();
}
};
worker.addEventListener('message', onReady);
});
} Type guard
function workerReady(exports) {
return exports != null && typeof exports.QRGenerator?.Generate === 'function';
} Try / catch
// In the worker message handler the throw is intentional; surface it:
self.postMessage({ command: 'response', requestId: e.data.requestId, error: err.message });
// Main thread:
if (resp.error && /worker exports not loaded/.test(resp.error)) retryAfterDelay(); Prevention
- Send a 'ready' message from the worker after getAssemblyExports resolves.
- Annotate exports with `[JSExport]` and verify the main assembly name.
- Surface `startupError` to distinguish boot failure from a race.
When it happens
Trigger: Posting a message to the worker before (or after a failed) `dotnet.create()` completed such that `assemblyExports` is null/undefined. Also if `getAssemblyExports` resolved to nothing or the config's mainAssemblyName was wrong.
Common situations: Worker boot race — main thread posts `generateQR` before startup finished; missing `[JSExport]` attribute on `Generate`; mainAssemblyName mismatch so `assemblyExports.QRGenerator` is undefined; .NET runtime resource (wasm/dll) failed to fetch (404/CORS).
Related errors
- QR code size must be less than {MaxQrSize}.
- Unknown command: ${e.data.command}
- No weather forecast!
- HttpContext not available
- Validation failed. Status Code: {response.StatusCode}
AI-assisted analysis of dotnet/AspNetCore.Docs@c67a80103a (2026-08-13).
Data as JSON: /api/errors/77ed506efb5b0b33.
Report an issue: GitHub.