salvo-static-files
Salvo Static File Serving
Setup
[dependencies]
salvo = { version = "0.89.3", features = ["serve-static"] }
rust-embed = "8" # only for embedded assets
Catch-all path segment uses {*path} (greedy, single segment required) or {**path} (greedy, may be empty).
StaticDir
StaticDir::new(roots) accepts one or more root directories tried in order (first match wins). Builder methods:
defaults(names)— file(s) to try when the URL points to a directory (e.g."index.html")fallback(name)— file served when no match is found (for SPAs)auto_list(bool)— enable directory listinginclude_dot_files(bool)— include files starting with.exclude(fn)— filter predicatechunk_size(u64)— streaming chunk sizecompressed_variation(algo, exts)— serve pre-compressed variants
Note: StaticDir does NOT expose a cache_control() builder. For cache headers, add a middleware that sets them on the response.
use salvo::prelude::*;
use salvo::serve_static::StaticDir;
#[tokio::main]
async fn main() {
let router = Router::with_path("{*path}").get(
StaticDir::new(["static", "public"])
.defaults("index.html")
.auto_list(true),
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Setting Cache Headers
Since StaticDir has no builder for cache headers, use a middleware:
use salvo::prelude::*;
use salvo::http::header::{CACHE_CONTROL, HeaderValue};
#[handler]
async fn set_cache(res: &mut Response) {
res.headers_mut().insert(
CACHE_CONTROL,
HeaderValue::from_static("public, max-age=31536000, immutable"),
);
}
let assets = Router::with_path("assets/{*path}")
.hoop(set_cache)
.get(StaticDir::new(["static/assets"]));
StaticFile (Single File)
use salvo::prelude::*;
use salvo::serve_static::StaticFile;
let router = Router::new()
.push(Router::with_path("favicon.ico").get(StaticFile::new("static/favicon.ico")))
.push(Router::with_path("robots.txt").get(StaticFile::new("static/robots.txt")));
StaticFile wraps a NamedFileBuilder, so it handles ETag, Range, and conditional requests.
Embedded Assets (rust-embed)
Embed files at compile time for single-binary deployment.
use rust_embed::RustEmbed;
use salvo::prelude::*;
use salvo::serve_static::static_embed;
#[derive(RustEmbed)]
#[folder = "dist"]
struct Assets;
#[tokio::main]
async fn main() {
let router = Router::with_path("{*path}").get(
static_embed::<Assets>().fallback("index.html"), // SPA fallback
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
StaticEmbed builder methods: defaults(names), fallback(name). No cache_control here either.
API + Static Hybrid
Put API routes before the catch-all static route so they match first.
use salvo::prelude::*;
use salvo::serve_static::StaticDir;
#[handler]
async fn api_users() -> Json<Vec<&'static str>> { Json(vec!["Alice", "Bob"]) }
#[tokio::main]
async fn main() {
let router = Router::new()
.push(Router::with_path("api/users").get(api_users))
.push(
Router::with_path("{*path}").get(
StaticDir::new(["static"]).defaults("index.html"),
),
);
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Protected Static Content
use salvo::prelude::*;
use salvo::serve_static::StaticDir;
use salvo::session::SessionDepotExt;
#[handler]
async fn require_login(depot: &mut Depot, res: &mut Response, ctrl: &mut FlowCtrl) {
let logged_in = depot
.session_mut()
.and_then(|s| s.get::<bool>("logged_in"))
.unwrap_or(false);
if !logged_in {
res.status_code(StatusCode::UNAUTHORIZED);
ctrl.skip_rest();
}
}
let router = Router::new()
.push(Router::with_path("public/{*path}").get(StaticDir::new(["static/public"])))
.push(
Router::with_path("private/{*path}")
.hoop(require_login)
.get(StaticDir::new(["static/private"])),
);
Custom Embedded Handler
If you need custom cache rules or content-type overrides, write a handler directly over RustEmbed::get.
use rust_embed::RustEmbed;
use salvo::http::header::{CACHE_CONTROL, CONTENT_TYPE, HeaderValue};
use salvo::prelude::*;
#[derive(RustEmbed)]
#[folder = "static"]
struct Assets;
#[handler]
async fn serve(req: &mut Request, res: &mut Response) {
let path = req.param::<String>("path").unwrap_or_default();
let Some(asset) = Assets::get(&path).or_else(|| Assets::get("index.html")) else {
res.status_code(StatusCode::NOT_FOUND);
return;
};
let ct = mime_guess::from_path(&path).first_or_octet_stream().to_string();
res.headers_mut().insert(CONTENT_TYPE, ct.parse().unwrap());
if path.contains('.') {
res.headers_mut().insert(
CACHE_CONTROL,
HeaderValue::from_static("public, max-age=31536000, immutable"),
);
}
let _ = res.write_body(asset.data.to_vec());
}
Compression
Compression builder takes CompressionLevel, not raw integers or flate2::Compression.
use salvo::compression::{Compression, CompressionLevel};
use salvo::prelude::*;
use salvo::serve_static::StaticDir;
#[tokio::main]
async fn main() {
let compression = Compression::new()
.enable_gzip(CompressionLevel::Default)
.enable_brotli(CompressionLevel::Default)
.min_length(1024);
let router = Router::new()
.hoop(compression)
.push(Router::with_path("{*path}").get(
StaticDir::new(["dist"]).defaults("index.html"),
));
let acceptor = TcpListener::new("0.0.0.0:8080").bind().await;
Server::new(acceptor).serve(router).await;
}
Requires compression feature on salvo.
Salvo-Specific Notes
{*path}= one or more segments;{**path}= zero or more.StaticDir/StaticEmbedhave nocache_controlbuilder — set headers via middleware.StaticDir::new([...])multi-root = lookup fallback order, great for theme overrides.- Use
fallback("index.html")for SPA client-side routing; usedefaults("index.html")only for directory index behavior.
Related Skills
- salvo-file-handling: File uploads and downloads via
NamedFile - salvo-compression: Response compression details
- salvo-caching: ETag/cache middleware
More from salvo-rs/salvo-skills
salvo-auth
Implement authentication and authorization using JWT, Basic Auth, or custom schemes. Use for securing API endpoints and user management.
15salvo-caching
Implement caching strategies for improved performance. Use for reducing database load and speeding up responses.
15salvo-proxy
Implement reverse proxy to forward requests to backend services. Use for load balancing, API gateways, and microservices routing.
15salvo-timeout
Configure request timeouts to prevent slow requests from blocking resources. Use for protecting APIs from long-running operations.
15salvo-openapi
Generate OpenAPI documentation automatically from Salvo handlers. Use for API documentation, Swagger UI, and API client generation.
15salvo-path-syntax
Path parameter syntax guide for Salvo routing. Explains the `{}` syntax (v0.76+) vs deprecated `<>` syntax, with migration examples.
14