The Heimdall Framework
Heimdall is an HTML-first, server-driven UI framework for ASP.NET Core. It keeps the browser simple, the server in control, and removes the need for a separate client-side application layer in many cases.
This page explains what Heimdall is, what problems it solves, and then gets you from zero to your first working page and interaction.
1. Why Heimdall Exists
Modern web apps often split UI across multiple layers: APIs, client state, and rendering frameworks. That split introduces complexity, duplication, and synchronization problems. Heimdall reduces that surface area by letting the server remain the source of truth and using HTML as the interaction boundary.
Traditional stack:
UI -> JSON API -> client state -> DOM updates
Heimdall:
UI -> HTML -> DOM updates2. What Heimdall Solves
3. What Heimdall Is Not
4. The Mental Model
Heimdall is built around a simple rule: the server owns the UI, and the browser applies updates.
Pages -> return HTML
Invocations -> return HTML
Browser -> swaps HTML into the DOM
No client-side state required5. Install
Install the Heimdall template package once, then choose the focused template that matches the app's ownership model. Visual Studio lists separate Native, MVC, Razor Pages, Blazor, and Static Site Generator entries; hosted templates expose only the authentication choices they support, while the static generator exposes artifact or web-root output. The Blazor template is static SSR and does not enable Interactive Server. If you only need server-side HTML composition, HeimdallFramework.Rendering can be installed without the full Server pipeline.
dotnet new install HeimdallFramework.Templates
# Native Heimdall application:
dotnet new heimdall-native -n MyHeimdallApp
# Other hosted ownership models:
dotnet new heimdall-mvc -n MyMvcApp
dotnet new heimdall-razor-pages -n MyRazorPagesApp
dotnet new heimdall-blazor -n MyBlazorApp
# Optional OpenID Connect scaffold for hosted apps:
dotnet new heimdall-native -n MyOidcApp --auth oidc
# Static-first application:
dotnet new heimdall-ssg -n MyStaticSite --output-mode artifact
dotnet new heimdall-ssg -n MyHostedStaticSite --output-mode web-root
# Standalone server-side HTML composition:
dotnet add package HeimdallFramework.Rendering6. Optional Server Setup
The generated hosted app does not require rate limiting, but when an application needs a native ASP.NET Core policy for content invocations, configure it beside AddHeimdall. Apply the policy with [EnableRateLimiting] on the invocation or invocation class; use [DisableRateLimiting] for a narrow opt-out. If rate limiting is not needed, omit the limiter registration and middleware.
using Microsoft.AspNetCore.RateLimiting;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHeimdall();
builder.Services.AddRateLimiter(options =>
{
options.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
options.AddFixedWindowLimiter("content-invocations", limiter =>
{
limiter.PermitLimit = 60;
limiter.Window = TimeSpan.FromMinutes(1);
limiter.QueueLimit = 0;
});
});
var app = builder.Build();
app.UseRateLimiter();
app.UseHeimdall();
[EnableRateLimiting("content-invocations")]
[ContentInvocation("profile.save")]
public static IHtmlContent Save(ProfilePayload payload)
=> Profile.Render(payload);7. Your First Page
A Heimdall page is just a route that returns HTML. No Razor is required.
app.MapHeimdallPage("/", ctx =>
FluentHtml.Div(div => div
.Text("Hello from Heimdall")));8. Your First Interaction
A Heimdall interaction sends a request to a server invocation, gets HTML back, and swaps that HTML into the DOM.
Strongly Typed Markup
return FluentHtml.Button(button => button
.Text("Click Me")
.Heimdall(heimdall => heimdall
.Click("counter.increment")
.Target("#result")
.SwapInner()));Rendered HTML
<button
heimdall-content-click="counter.increment"
heimdall-content-target="#result"
heimdall-content-swap="inner">
Click Me
</button>
<div id="result"></div>Invocation
[ContentInvocationPrefix("counter")]
public sealed class CounterInvocations
{
[ContentInvocation("increment")]
public IHtmlContent Increment()
{
return FluentHtml.Div(div => div
.Text("Updated from the server."));
}
}9. What Just Happened
10. Newer Capabilities
These focused guides cover the newer production-facing features without hiding them inside the older reference pages.
Reusable Libraries and Frontend Plugins
Package server-rendered content and browser integrations through explicit public extension points.
Next Steps
Once you understand the basic model, these are the next concepts to explore.