Cookbook / Files and pages
Static files and single-page apps
Serve a directory with range requests and caching, and fall back to index.html for client-side routes.
static_dir streams files from disk with range requests, ETag/304 handling and correct content types. spa_fallback serves your index.html for any path that is neither a file nor an API route, so deep links in a React, Vue or Svelte app keep working.
use axeon::{Json, Router, Server};
use axeon::json;
fn main() {
let mut app = Server::new();
// API routes win over files.
let mut api = Router::new();
api.get("/hello", |_req| async { Json(json!({ "hello": "world" })) });
app.mount("/api", api);
app.static_dir("public") // ./public/**
.static_cache_control("public, max-age=3600")
.spa_fallback("index.html"); // /dashboard/settings -> index.html
app.listen("127.0.0.1:3000").expect("server failed to start");
}
mkdir public && echo '<h1>My app</h1>' > public/index.html
curl -i http://127.0.0.1:3000/ # index.html
curl -i http://127.0.0.1:3000/dashboard/stats -H 'accept: text/html' # index.html again (SPA route)
curl -i http://127.0.0.1:3000/api/hello # JSON
Notes#
- The fallback only answers
GETandHEADrequests that accept HTML, for paths without a file extension. A missing/app.jsor an API call still gets a real404instead of your HTML page. - Path traversal is refused:
..segments, backslashes and absolute paths never escape the directory. - Give hashed build assets (
app.3f9a1c.js) a longmax-ageand keepindex.htmlshort-lived, so deploys are picked up immediately. - Pair with compression for text assets.