Skip to content

The basics

Static files

Serve public assets through os.Root with traversal protection and long-lived caching for fingerprinted files.

Last updated:

middleware.Static serves files from a directory when they exist and passes every other request on to the router.

Go
root, err := os.OpenRoot("public")
if err != nil {
	return err // directory missing
}
app.OnShutdown(func(context.Context) error { return root.Close() }) // the caller owns root

r.UseServer(middleware.Static(root, middleware.StaticConfig{}))

Register it as server middleware so files are served without a matching route.

Safety#

  • Files are opened through an os.Root, so .. segments and symlinks cannot escape the directory.
  • Dotfiles (.env, .git/…) and any path with a dot segment are never served.
  • Directory listings are never served.

Caching#

FilesCache-Control
Under /build/ (default ImmutablePrefixes) or fingerprinted names such as app-3f9a1c2b.jspublic, max-age=31536000, immutable
Everything elsepublic, max-age=0, must-revalidate

http.FileServerFS handles Range, If-Modified-Since and content types.

Options#

Go
middleware.StaticConfig{
	Prefix:            "/assets",                // serve /assets/logo.svg from public/logo.svg
	ImmutablePrefixes: []string{"/assets/build/"},
}

Embedded assets#

middleware.StaticFS serves any fs.FS, such as an embed.FS, to ship a single binary. The starter does this with resources.Public(), which uses resources/public on disk during development and the embedded copy otherwise:

Go
//go:embed public
var publicFS embed.FS

sub, err := fs.Sub(publicFS, "public")
if err != nil {
	return err
}
r.UseServer(middleware.StaticFS(sub, middleware.StaticConfig{}))

Uploads#