Pages
A Heimdall page is a route that returns HTML. You map it with MapHeimdallPage(...), render with your existing HTML helpers, and choose the overload that matches how much context your page needs.
Pages are route-level HTML endpoints. Content invocations are for triggered interactions that return fragment HTML.
1. What a Page Is
Pages are the route-level entry points of a Heimdall app. They handle a URL and return HTML for that request.
app.MapHeimdallPage("/", () =>
{
return Html.Div(d =>
{
d.Text("Hello from Heimdall");
});
});2. The Available Overloads
Heimdall provides several MapHeimdallPage overloads so you can keep simple pages simple while still supporting pages that need HttpContext, services, or async work.
3. Fully Static Pages
Use the simplest overload when the page does not need HttpContext or services.
app.MapHeimdallPage("/about", () =>
{
return Html.Div(d =>
{
d.H1(h => h.Text("About"));
d.P(p => p.Text("This page is fully static."));
});
});4. Pages That Need HttpContext
Use the HttpContext overload when rendering depends on request information like the current user, headers, route values, query string, or request path.
app.MapHeimdallPage("/profile", ctx =>
{
var name = ctx.User.Identity?.Name ?? "Anonymous";
return Html.Div(d =>
{
d.H1(h => h.Text("Profile"));
d.P(p => p.Text($"Hello, {name}."));
});
});5. Async Pages with HttpContext
Use the async HttpContext overload when the page needs asynchronous work but does not need direct access to IServiceProvider.
app.MapHeimdallPage("/dashboard", async ctx =>
{
await Task.Delay(10);
return Html.Div(d =>
{
d.H1(h => h.Text("Dashboard"));
d.P(p => p.Text("Rendered asynchronously."));
});
});6. Pages That Need Services and Context
Use the IServiceProvider + HttpContext overload when the page needs access to registered services while still remaining an explicit route-level function.
app.MapHeimdallPage("/account", (sp, ctx) =>
{
var clock = sp.GetRequiredService<ISystemClock>();
return Html.Div(d =>
{
d.H1(h => h.Text("Account"));
d.P(p => p.Text($"Rendered at {clock.UtcNow:u}."));
});
});7. The Full Async Overload
This is the core primitive behind the other overloads. Use it when you need both request context and asynchronous service-driven rendering.
app.MapHeimdallPage("/reports", async (sp, ctx) =>
{
var repository = sp.GetRequiredService<IReportRepository>();
var reports = await repository.GetRecentAsync(ctx.RequestAborted);
return Html.Div(d =>
{
d.H1(h => h.Text("Reports"));
d.Ul(ul =>
{
foreach (var report in reports)
{
ul.Li(li => li.Text(report.Title));
}
});
});
});8. Pages Return HTML
Every overload ultimately returns IHtmlContent. Heimdall renders that content to an HTML response with a text/html content type.
Page Method
public static class RoutingPage
{
public static IHtmlContent Render()
{
return FluentHtml.Main(main =>
{
main.Div(d =>
{
d.H1(h => h.Text("Pages"));
d.P(p => p.Text("Rendered on the server."));
});
});
}
}Mapped Route
app.MapHeimdallPage("/pages", () => RoutingPage.Render());9. Pages Usually Use a Layout
In a real app, pages usually render inside a shared layout. The layout provides the document shell, navigation, scripts, styles, and global targets like toast containers.
public static IHtmlContent Render(HttpContext ctx)
{
return MainLayout.Render(ctx, body =>
{
body.Main(main =>
{
main.Class(Bootstrap.Spacing.Py(5));
main.Div(container =>
{
container.Class(Bootstrap.Layout.Container);
container.H1(h1 => h1.Text("Pages"));
});
});
});
}10. Pages vs Actions
This is one of the most important distinctions in Heimdall.
Pages
Mapped with MapHeimdallPage(...). Used for route-level rendering when a browser requests a page.
- Handle URLs like /, /pages, or /forms
- Return HTML for the request
- Usually wrapped in a layout
Actions
Marked with [ContentInvocation]. Used for triggered interactions that return fresh HTML fragments.
- Triggered by Heimdall attributes
- Return fragment HTML
- Typically swapped into an existing target
11. Typical Page Pattern
A common pattern is to keep each route in its own page class with a static Render(...) method. This keeps route mapping simple and page composition easy to follow.
app.MapHeimdallPage("/", () => IndexPage.Render());
app.MapHeimdallPage("/pages", () => RoutingPage.Render());
app.MapHeimdallPage("/actions", () => ActionsPage.Render());Next Steps
Once pages make sense, the next thing to learn is how interactions work.