dotnet/runtime · error · Error

Failed to load resource

Error message

Failed to load resource '${asset.name}' from '${asset.resolvedUrl}'. HTTP status: ${response.status} ${response.statusText}

What it means

Thrown by fetchBytes() when the HTTP response was obtained but response.ok is false AND the asset is not marked optional. This is a real server reply with an error status (4xx/5xx). The status code and statusText tell you exactly what the server returned. Contrast with 101 which fires when no response is obtained at all.

Solutions

  1. Run dotnet publish again and confirm the file exists under bin/Release/net*/publish/wwwroot/_framework/.
  2. Match the exact filename (case, extension) the boot manifest references against what is deployed.
  3. Add anonymous static-file auth for /_framework or pass credentials via asset.useCredentials=true.
  4. Fix server MIME types (application/wasm for .wasm) and rewrite rules so the file is served.
  5. If the asset is genuinely optional (e.g. satellite PDB), mark isOptional=true or set loaderConfig.ignorePdbLoadErrors=true.

Example fix

// before: 404 because satellite .dll not published
// after: ensure publish includes all satellites
dotnet publish -c Release --self-contained
// or mark optional
loaderConfig.ignorePdbLoadErrors = true;
Defensive patterns

Strategy: validation

Validate before calling

async function assetExists(url: string): Promise<boolean> {
  const r = await fetch(url, { method: 'HEAD' });
  return r.ok;
}
// before create():
for (const a of loaderConfig.resources!.assembly) {
  if (!await assetExists(a.resolvedUrl!)) console.warn('Missing', a.name);
}

Type guard

function hasOkStatus(r: Response): boolean { return r.ok; }

Try / catch

try { await dotnet.create(); } catch (e) {
  const m = (e as Error).message.match(/HTTP status: (\d{3})/);
  if (m) handleHttpError(Number(m[1]));
  else throw e;
}

Prevention

When it happens

Trigger: HTTP 404 because the asset was not published to wwwroot/_framework. HTTP 401/403 from authenticated endpoints. HTTP 500 from a misconfigured server. asset.isOptional is unset (default) so any non-2xx aborts startup. Server returns 200 with wrong content but wrong status set by a custom callback.

Common situations: Publishing the app but forgetting 'dotnet publish' so _framework is stale/missing assemblies. Renaming a DLL and not republishing. CDN cache serving a stale 404. IIS missing MIME map for .wasm/.dll (would actually 404 the file). Authentication cookie expired so the static file endpoint returns 401. web.config rewrite rules swallowing the file path.

Related errors


AI-assisted analysis of dotnet/runtime@60108ba66e (2026-08-10). Data as JSON: /api/errors/2f586be136b15e4c. Report an issue: GitHub.

Appendix: source

Thrown at src/native/libs/Common/JavaScript/loader/assets.ts:363

    dotnetDiagnosticsExports.installNativeSymbols(tableText || "");
}

async function fetchBytes(asset: AssetEntryInternal): Promise<Uint8Array | null> {
    dotnetAssert.check(asset && asset.resolvedUrl, "Bad asset.resolvedUrl");
    let response: Response;
    try {
        response = await loadResource(asset);
    } catch (err: any) {
        // Strip .silent flag from download errors so they are properly reported via exit listeners
        const message = err instanceof Error ? err.message : String(err);
        throw new Error(`Failed to load resource '${asset.name}' from '${asset.resolvedUrl}': ${message}`, { cause: err });
    }
    if (!response.ok) {
        if (asset.isOptional) {
            dotnetLogger.warn(`Optional resource '${asset.name}' failed to load from '${asset.resolvedUrl}'. HTTP status: ${response.status} ${response.statusText}`);
            return null;
        }
        throw new Error(`Failed to load resource '${asset.name}' from '${asset.resolvedUrl}'. HTTP status: ${response.status} ${response.statusText}`);
    }
    const buffer = await (asset.buffer || response.arrayBuffer());
    return new Uint8Array(buffer);
}

async function fetchText(asset: AssetEntryInternal): Promise<string | null> {
    dotnetAssert.check(asset && asset.resolvedUrl, "Bad asset.resolvedUrl");
    let response: Response;
    try {
        response = await loadResource(asset);
    } catch (err: any) {
        // Strip .silent flag from download errors so they are properly reported via exit listeners
        const message = err instanceof Error ? err.message : String(err);
        throw new Error(`Failed to load resource '${asset.name}' from '${asset.resolvedUrl}': ${message}`, { cause: err });
    }
    if (!response.ok) {
        if (asset.isOptional) {
            dotnetLogger.warn(`Optional resource '${asset.name}' failed to load from '${asset.resolvedUrl}'. HTTP status: ${response.status} ${response.statusText}`);

View on GitHub (pinned to 60108ba66e)