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.
Same-origin frontend:
browser -> relative Heimdall endpoints
Separate-origin frontend:
browser frontend -> HTML/action origin
solve with CORS, authentication, absolute endpoints,
and a trusted boundary2. 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.
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 UI3. 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.
<!-- 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><!-- 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.
Ordinary integration:
HTML endpoint or content action -> target swap
Optional live push:
Bifrost topic -> EventSource -> HTML swapIf 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.
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.
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.
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.
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?Next Steps
Choose the guide that matches the boundary you are building: Patterns for rendering-only HTML endpoints, runtime behavior for browser ownership, security for cross-origin policy, SSE if you need live push, or configuration for the exact switches.