원클릭으로
salvo-static-files
Serve static files, directories, and embedded assets. Use for CSS, JavaScript, images, and downloadable content.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Serve static files, directories, and embedded assets. Use for CSS, JavaScript, images, and downloadable content.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Implement authentication and authorization using JWT, Basic Auth, or custom schemes. Use for securing API endpoints and user management.
Create basic Salvo web applications with handlers, routers, and server setup. Use when starting a new Salvo project or adding basic HTTP endpoints.
Implement caching strategies for improved performance. Use for reducing database load and speeding up responses.
Compress HTTP responses using gzip, brotli, zstd, or deflate. Use for reducing bandwidth and improving load times.
Limit concurrent requests to protect resources. Use for file uploads, expensive operations, and preventing resource exhaustion.
Configure Cross-Origin Resource Sharing (CORS) and security headers. Use for APIs accessed from browsers on different domains.
| name | salvo-static-files |
| description | Serve static files, directories, and embedded assets. Use for CSS, JavaScript, images, and downloadable content. |
| version | 0.94.0 |
| tags | ["data","static-files","assets","serve-static"] |
[dependencies]
salvo = { version = "0.94.0", 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::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 variantsNote: 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;
}
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"]));
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.
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.
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;
}
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"])),
);
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 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.
{*path} = one or more segments; {**path} = zero or more.StaticDir/StaticEmbed have no cache_control builder — set headers via middleware.StaticDir::new([...]) multi-root = lookup fallback order, great for theme overrides.fallback("index.html") for SPA client-side routing; use defaults("index.html") only for directory index behavior.NamedFile