dotnet/aspnetcore · error · Error

For a ${resourceType} resource, custom loaders must supply a

Error message

For a ${resourceType} resource, custom loaders must supply a URI string.

What it means

Thrown by importDotnetJs when a custom loadBootResource callback returns a truthy, non-string value for the 'dotnetjs' resource type. The dotnet.js module must be loaded via dynamic import(), which only accepts a URI string — a Response/Request/object is not valid here, unlike other resource types the custom loader can return.

Source

Thrown at src/Components/Web.JS/src/Platform/Mono/MonoPlatform.ts:137

};

async function importDotnetJs(startOptions: Partial<WebAssemblyStartOptions>): Promise<ModuleAPI> {
  const browserSupportsNativeWebAssembly = typeof WebAssembly !== 'undefined' && WebAssembly.validate;
  if (!browserSupportsNativeWebAssembly) {
    throw new Error('This browser does not support WebAssembly.');
  }


  // Allow overriding the URI from which the dotnet.*.js file is loaded
  if (startOptions.loadBootResource) {
    const resourceType: WebAssemblyBootResourceType = 'dotnetjs';
    const customSrc = startOptions.loadBootResource(resourceType, 'dotnet.js', '_framework/dotnet.js', '', 'js-module-dotnet');
    if (typeof (customSrc) === 'string') {
      const absoluteSrc = (new URL(customSrc, document.baseURI)).toString();
      return await import(/* webpackIgnore: true */ absoluteSrc);
    } else if (customSrc) {
      // Since we must load this via a import, it's only valid to supply a URI (and not a Request, say)
      throw new Error(`For a ${resourceType} resource, custom loaders must supply a URI string.`);
    }
  }

  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
  // @ts-ignore: This dynamic import is handled at runtime and does not need a type declaration.
  return await import(/* webpackIgnore: true */ './dotnet.js');
}

function prepareRuntimeConfig(options: Partial<WebAssemblyStartOptions>, onConfigLoadedCallback?: (loadedConfig: MonoConfig) => void): DotnetModuleConfig {
  const config: MonoConfig = {
    maxParallelDownloads: 1000000, // disable throttling parallel downloads
    enableDownloadRetry: false, // disable retry downloads
  };

  if (options.environment) {
    config.applicationEnvironment = options.environment;
  }

View on GitHub (pinned to 294cab2f9b)

Solutions

  1. For the 'dotnetjs' resource type, return a URI string (the URL to your cached/CDN dotnet.js), or return null/undefined to use the default.
  2. Branch loadBootResource on resourceType and only override dotnetjs with a string URL.
  3. If you must cache, precache the file and return its object URL string.

Example fix

// before
loadBootResource: (type, name, uri) => cache.match(uri) // Response — invalid for dotnetjs

// after
loadBootResource: (type, name, uri) =>
  type === 'dotnetjs' ? `${cdnBase}/dotnet.js` : cache.match(uri)
Defensive patterns

Strategy: validation

Validate before calling

// Validate loadBootResource return for dotnetjs.
function loadBootResource(type, name, uri) {
  if (type === 'dotnetjs') {
    const url = `${cdnBase}/${name}`;
    return typeof url === 'string' ? url : undefined; // string or default
  }
  return fetch(uri);
}

Type guard

function isStringUrl(v: unknown): v is string {
  return typeof v === 'string' && v.length > 0;
}

Prevention

When it happens

Trigger: Implementing options.loadBootResource and returning a Response, Request, or object for the resourceType === 'dotnetjs' case instead of a URL string.

Common situations: Writing a generic loadBootResource that uniformly returns fetch() Responses for all resource types; CDN/offline cache strategies that try to serve dotnet.js from a Cache Response.

Related errors


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/4598b3ab9c1c1179. Report an issue: GitHub.