dotnet/aspnetcore · error

Root components cannot be marked as interactive. The <html>

Error message

Root components cannot be marked as interactive. The <html> element must be rendered statically so that scripts are not evaluated multiple times.

What it means

Thrown by assertNotDirectlyOnDocument (ComponentDescriptorDiscovery.ts:181) when a component marker's direct parent is the Document node itself — i.e. someone tried to make the <html> root element an interactive (Server/WebAssembly/Auto) component. Blazor requires the root <html> to remain static SSR so that <script> tags are not re-evaluated when interactive rendering re-renders the tree, which would double-load scripts and corrupt state.

Source

Thrown at src/Components/Web.JS/src/Services/ComponentDescriptorDiscovery.ts:181

    } else {
      return;
    }
  }
}

function parseCommentPayload(json: string): ServerComponentMarker | WebAssemblyComponentMarker | AutoComponentMarker {
  const payload = JSON.parse(json);
  const { type } = payload;
  if (type !== 'server' && type !== 'webassembly' && type !== 'auto') {
    throw new Error(`Invalid component type '${type}'.`);
  }

  return payload;
}

function assertNotDirectlyOnDocument(marker: Node) {
  if (marker.parentNode instanceof Document) {
    throw new Error('Root components cannot be marked as interactive. The <html> element must be rendered statically so that scripts are not evaluated multiple times.');
  }
}

function getComponentEndComment(payload: ComponentMarker, start: Comment, iterator: ComponentCommentIterator): Comment | undefined {
  const { prerenderId } = payload;
  if (!prerenderId) {
    return undefined;
  }

  while (iterator.next() && iterator.currentElement) {
    const node = iterator.currentElement;
    if (node.nodeType !== Node.COMMENT_NODE) {
      continue;
    }
    if (!node.textContent) {
      continue;
    }

View on GitHub (pinned to 294cab2f9b)

Solutions

  1. Keep rendermode attributes off the root document layout; apply @rendermode only to nested components below <html>/<head>/<body>.
  2. Render the app shell statically (SSR) and mark individual routable components or sub-trees as interactive.
  3. Review App.razor / Routes.razor and ensure InteractiveServer/InteractiveAuto is on the Router content, not on a component that owns <html>.
  4. If you need interactive head content, use <HeadOutlet> with an appropriate rendermode rather than re-rendering <html>.

Example fix

<!-- before: App.razor root marked interactive -->
<!DOCTYPE html>
<html lang="en">
  <head><component type="typeof(App)" render-mode="InteractiveServer" /></head>

<!-- after: static shell, interactive router inside -->
<!DOCTYPE html>
<html lang="en">
  <head><HeadOutlet /></head>
  <body>
    <Routes @rendermode="InteractiveServer" />
    <script src="_framework/blazor.web.js"></script>
  </body>
</html>
Defensive patterns

Strategy: validation

Validate before calling

function assertRootIsStatic(rootComponentType: string, renderMode?: string) {
  if (renderMode && renderMode !== 'StaticServer' && rootComponentType === 'html') {
    throw new Error('Do not apply an interactive rendermode to the document root.');
  }
}

Type guard

function isSafeRenderModeForRoot(mode: string | undefined): boolean {
  return !mode || mode === 'StaticServer';
}

Prevention

When it happens

Trigger: Registering a root component whose Razor output targets the document/html element with an interactive rendermode; App.razor or _Host equivalent placing an InteractiveServer/InteractiveAuto rendermode attribute such that the marker's parent resolves to Document; misconfigured Routes.razor with a rendermode on a component that wraps <html>.

Common situations: Migrating a legacy Blazor Server _Host.cshtml and accidentally applying @rendermode InteractiveServer at the document root; putting <HeadOutlet> or root components in a way that the top-level marker lands directly under document; custom IComponentRenderMode assignments on root components.

Related errors


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