Single Page Applications (SPA) ​
HTTPX provides native Single Page Application (SPA) routing, automatically resolving static assets and falling back to index.html for client-side routing.
SPA Fallback Configuration ​
Modern client-side frameworks (React, Vue, Svelte, Solid, Angular) rely on the HTML5 History API (pushState). When a user refreshes deep URLs like /dashboard/settings, the server must serve index.html rather than 404 Not Found.
zig
const std = @import("std");
const httpx = @import("httpx");
pub fn main() !void {
var gpa: std.heap.DebugAllocator(.{}) = .init;
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const io = std.Io.Threaded.global_single_threaded.io();
var server = try httpx.Server.init(allocator, io, .{ .port = 8080 });
defer server.deinit();
// API routes take precedence (register code routes before the SPA mount)
try server.get("/api/status", statusHandler);
// SPA fallback: serves existing files from ./dist, or falls back to index.html
try server.spa("/", "./dist");
server.run();
}
fn statusHandler(ctx: *httpx.router.Context) anyerror!httpx.router.Response {
_ = ctx;
return .{ .status = 200, .body = "{\"status\":\"ok\"}", .contentType = "application/json" };
}Resolution Flow & Fallback Policy ​
HTTPX uses an explicit, controlled SPA fallback policy rather than returning index.html indiscriminately:
- API & Explicit Code Routes: Checked first. Known routes and handlers registered before
server.spa()execute with their normal responses. - Physical Asset Resolution: If the request path maps to a real file in the document root (or embedded asset registry), it is served with appropriate MIME types,
ETag, and conditional request caching. - Controlled Fallback Determination (
shouldSpaFallback):- HTTP Method: Fallback is restricted to
GETandHEADrequests. Other methods (POST, PUT, DELETE) return404 Not Found. - Excluded Prefixes: Paths starting with configured
excludePrefixes(e.g./api,/auth) never fall back and return404 Not Found. - Asset Requests: Missing files with asset extensions (e.g.
.js,.mjs,.css,.png,.jpg,.svg,.ico,.woff2,.map,.json,.wasm) return404 Not Foundrather thanindex.html, preventing browser syntax errors (Unexpected token '<'). - Content Negotiation: If the client provides an
Acceptheader that explicitly does not permittext/html(e.g.Accept: application/jsonorAccept: image/*), fallback is declined.
- HTTP Method: Fallback is restricted to
- Application Navigation: Deep links and client routes (e.g.
/dashboard,/users/42/profile,/settings) that pass these checks receive the SPA fallback (index.html) with status 200.
Configuration Options ​
zig
try httpx.web.spa.register(&server.router, .{
.root = "./dist",
.fallback = "index.html",
.mount = "/",
.excludePrefixes = &.{ "/api", "/auth" },
.cacheControl = "public, max-age=3600",
.liveReload = true,
});