Static Files and Assets
Dusty serves files from a directory on disk, and files compiled into the binary, either one at a time or a whole directory of assets with cache-busting URLs.
Files from a Directory
router.static serves a directory under a prefix:
var dir = try std.Io.Dir.cwd().openDir(init.io, "examples/static", .{});
defer dir.close(init.io);
server.router.static("/static", dir, .{
.cache_control = "public, max-age=3600",
});
A request for /static/style.css gets style.css from the directory, with the content type from
its extension. A request for the directory gets its index.html. /static redirects to /static/.
The directory has to stay open while the server runs.
A path with .. in it gets a 404. So does a dotfile like /static/.env, unless you set
.hide_dotfiles = false.
Responses have ETag and Last-Modified headers. A conditional request for an unchanged file gets
a 304, and a Range request gets the part it asked for. .cache_control is sent as
Cache-Control.
With .precompressed = &.{ .br, .gzip }, a compressed copy next to a file, like style.css.br, is
sent in place of style.css to clients that accept it. The plain file has to exist too.
See http.StaticOptions for the other options.
Embedded Files
router.embedded serves one file that is compiled into the binary, usually with @embedFile:
The content type comes from the path's extension, unless .content_type says otherwise. The
ETag is a hash of the content. Compressed copies can be passed as .br, .zstd and .gzip.
Assets
addAssets in build.zig turns a directory into a module that embeds its files. This is from
examples/todo:
Here dusty is @import("dusty") at the top of build.zig, and dusty_dep is the dependency from
b.dependency("dusty", ...). The files are served under /assets. Set .prefix to change that.
Templates link to the files with assets.url, which adds a hash of the content to the URL, like
/assets/app.css?v=6ec8ea7d:
pub templ Layout(title: []const u8) {
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="htmx-config" content={htmx_config} />
<title>{title}</title>
<link rel="icon" href={assets.url("favicon.svg")} type="image/svg+xml" />
<link rel="stylesheet" href={assets.url("app.css")} />
<script src="https://unpkg.com/htmx.org@4.0.0/dist/htmx.min.js" integrity="sha384-BvJpBiO8Kh31EqtJe5DRIeWrHWnCGkwytKs9NKFi86Hhw96dEqdEMzZDeK9iEGTc" crossorigin="anonymous"></script>
<script src="https://unpkg.com/htmx.org@4.0.0/dist/ext/hx-sse.min.js" integrity="sha384-VZD0TLKqhJ26ayBUgQg3ud6DsOLMJvtcz0ANpNc9WSbgIuQTnlXI2IfsF5jhBjT6" crossorigin="anonymous"></script>
</head>
<body hx-boost:inherited="true">
@children
{http.dev.script(.{})}
</body>
</html>
}
A name that isn't in the directory is a compile error.
assets.register adds the routes that serve them:
A request with the current hash is answered with Cache-Control: public, max-age=31536000,
immutable, so browsers keep it for good. When the file changes, so does its URL. Any other request
gets the current file with no-cache, so a page that was open during a deploy still gets its
styles. A compressed copy next to a file, like app.css.br, is served to clients that accept it.
Next Steps
- WebSocket covers keeping a connection open for messages.
- Deployment covers running the server in production.