Skip to documentation
Heimdall Docs
Heimdall Docs

Frontend Integration

Heimdall can work with a server-rendered app, a hybrid frontend, or a browser application hosted separately from the HTML backend. The reliable boundary is still rendered HTML: the server owns markup, while the browser owns transport, local presentation, or a deliberately isolated island.

This page covers frontend boundaries, DOM ownership, Blazor and other browser frameworks, and cross-origin requests. Server-rendered pages are the baseline; this guide focuses on the cases where another browser framework, origin, or deployment boundary participates.

1. What Distributed Frontend Means

Here, distributed frontend means that the browser application and the HTML/action backend have separate responsibilities. They may share an origin, or the browser application may be hosted elsewhere. Only the separate-origin case needs CORS and cross-origin authentication.

Text
Same-origin frontend:
browser -> relative Heimdall endpoints

Separate-origin frontend:
browser frontend -> HTML/action origin
solve with CORS, authentication, absolute endpoints,
and a trusted boundary

2. Frontend Shapes and Support Boundaries

Heimdall is strongest when it owns a stable HTML boundary. Blazor, React, Vue, Svelte, and similar frontends can coexist through isolated HTML islands or explicit HTML insertion, but Heimdall is not a framework component adapter and does not reconcile a virtual DOM.

Text
Hybrid HTML island:
Blazor / React / Vue / Svelte
+ stable Heimdall DOM island
-> HTML outside the framework-owned subtree

Separate frontend origin:
browser frontend
+ CORS + bearer/requestHeaders
-> explicit cross-origin HTML actions

Explicit SPA integration:
frontend requests HTML
+ app-owned insertion boundary
-> fragment becomes current UI

3. The Hybrid HTML-Island Pattern

A browser framework can host Heimdall content, but each DOM subtree needs one owner. Put Heimdall markup in a stable element that the framework does not render, and let the Heimdall runtime manage that island. Do not target elements that a later framework render may replace.

HTML
<!-- A stable island outside framework-owned markup. -->
<section id="account-panel">
  <button
    heimdall-content-click="Account.Refresh"
    heimdall-content-target="#account-panel"
    heimdall-content-swap="inner">
    Refresh
  </button>
</section>

<!-- Same-origin or reverse-proxied asset path. -->
<script src="/_content/HeimdallFramework.Web/heimdall-bundle.min.js"></script>
HTML
<!-- Separate origin: load from the backend or proxy this path. -->
<script src="https://html.example.com/_content/HeimdallFramework.Web/heimdall-bundle.min.js"></script>

The same rule applies to a standalone WASM or SPA frontend: it can display Heimdall HTML if the runtime bundle is available and a stable target exists. The current Web package ships the browser bundle as an ASP.NET Core static asset, so a separate frontend must use the backend's absolute asset URL, a reverse proxy, or a deliberate copied-asset step. The browser frontend consumes server-produced HTML; it does not currently use Heimdall.Rendering to generate that HTML locally.

4. Optional Live Push with Bifrost

Bifrost is optional. Use it only when the backend needs to push HTML after the original request has finished. A traditional page and a separate frontend can both use it, but ordinary HTML actions and HTML endpoints do not require Bifrost.

Text
Ordinary integration:
HTML endpoint or content action -> target swap

Optional live push:
Bifrost topic -> EventSource -> HTML swap

If live push is needed, see SSE / Bifrost for topic authorization, signed tokens, reconnection, and multi-instance delivery. Those concerns are intentionally kept out of the ordinary frontend integration path.

5. Cross-Origin HTML Actions

A frontend hosted on another origin can invoke Heimdall content actions, but the browser treats the request as cross-origin. Configure an explicit CORS policy on the HTML backend and use a non-ambient authentication mechanism such as a bearer token. Do not treat CORS as authentication; it only controls which browsers may read or send the request.

C#
builder.Services.AddCors(options =>
    options.AddPolicy("frontend", policy => policy
        .WithOrigins("https://app.example.com")
        .WithMethods("POST", "OPTIONS")
        .WithHeaders(
            "Content-Type",
            "Authorization",
            "X-Heimdall-Content-Action",
            "RequestVerificationToken",
            "X-Heimdall-Client-Info")));

app.UseCors("frontend");
app.UseHeimdall();

6. Supplying Authentication Asynchronously

The browser runtime supports an asynchronous requestHeaders hook. Use it to obtain a current bearer token and return it only for the trusted backend origin and request kinds your application expects. The hook participates in content actions and antiforgery-token requests; it also participates in Bifrost token requests when live push is enabled.

JavaScript
Heimdall.config.requestHeaders = async context => {
  const backendOrigin = "https://api.example.com";

  if (new URL(context.url).origin !== backendOrigin)
    return null;

  if (context.kind !== "content-action" &&
      context.kind !== "bifrost-token")
    return null;

  return {
    Authorization: `Bearer ${await auth.getToken({
      signal: context.signal
    })}`
  };
};

7. Antiforgery: Keep It, Narrow It, or Turn It Off

Antiforgery is enabled by default because cookie-authenticated browser requests carry ambient credentials. A separate frontend using bearer tokens may have a different threat model, but disabling antiforgery is not a shortcut around authentication or authorization. Decide at the server boundary, then keep the browser setting aligned before the first action.

C#
Global opt-out when another protection makes cross-site
requests harmless, such as no ambient cookie auth:

builder.Services.AddHeimdall(options =>
    options.EnableAntiforgery = false);

// Browser configuration must match before first use.
Heimdall.config.antiforgery = false;

Narrow exception for one action:
[RequireAntiforgeryToken(false)]
public static IHtmlContent PublicFragment() => ...;

8. A Practical Troubleshooting Order

When a distributed frontend does not update, work from the boundary inward. This usually identifies whether the problem is asset loading, DOM ownership, CORS, authentication, or the returned HTML fragment.

C#
1. Is heimdall-bundle loaded and free of startup errors?
2. Does the target exist after the frontend renders?
3. Is the target outside a Blazor/component-owned subtree?
4. Does the preflight allow the actual method and headers?
5. Does requestHeaders return the expected bearer token?
6. Is the HTML fragment valid for the selected swap?