Documentation
¶
Overview ¶
Package static serves static files from the OS filesystem or an fs.FS.
Basic usage with an OS directory:
server.Use(static.New(static.Config{Root: "./public"}))
With an embedded filesystem:
//go:embed assets/*
var assets embed.FS
server.Use(static.New(static.Config{FS: assets}))
With a URL prefix:
server.Use(static.New(static.Config{
Root: "./public",
Prefix: "/static",
}))
Prefix Matching ¶
The Prefix is matched at segment boundaries. A prefix of "/api" will match "/api/file.txt" but NOT "/api-docs/file.txt". This prevents unintended path collisions.
Directory Browsing ¶
When Browse is enabled and the request path resolves to a directory without an index file, an HTML directory listing is rendered. Filenames in the listing are URL-encoded in href attributes and HTML-escaped in display text to prevent XSS via crafted filenames.
SPA Mode ¶
When Config.SPA is true the middleware operates in single-page application mode: requests for non-existent files serve the index file (default index.html) instead of falling through to the next handler. This allows client-side routers to handle arbitrary URL paths. Existing files and directories are still served normally.
Caching ¶
The middleware sets Last-Modified and ETag headers based on file metadata. Conditional requests (If-Modified-Since, If-None-Match) return 304 Not Modified when the client already has a fresh copy.
The ETag is a weak validator computed from the file's modification time and size: W/"<mtime_hex>-<size_hex>". For embed.FS files where ModTime is zero, caching headers are omitted.
Cache-Control ¶
Config.MaxAge sets the Cache-Control max-age directive. When MaxAge is greater than zero the response includes a "public, max-age=N" header (where N is seconds). When MaxAge is zero (the default) no Cache-Control header is added. MaxAge works alongside ETag and Last-Modified: browsers that have a cached copy within the max-age window skip the network entirely, and once the window expires they can still use conditional requests to validate freshness.
Range Requests ¶
OS files support range requests via the core celeris.Context.FileFromDir method. For fs.FS files, the middleware implements range request handling inline, supporting single byte ranges (bytes=start-end). When the underlying fs.File implements io.ReadSeeker, range requests seek to the requested offset and read only the required bytes instead of loading the entire file into memory.
Content-Type Detection ¶
Content types are determined by file extension via mime.TypeByExtension. When the extension is unknown or absent, the middleware sniffs the first 512 bytes using http.DetectContentType (for fs.FS files) or delegates to the core file-serving method (for OS files).
Pre-Compressed Files ¶
When Config.Compress is true, the middleware checks for pre-compressed variants of the requested file before serving the original. It inspects the Accept-Encoding request header and looks for a .br (Brotli) or .gz (gzip) file alongside the original. Brotli is preferred when both are accepted. The response includes Content-Encoding and Vary headers.
Pre-compressed file serving only applies to OS filesystem (Root), not fs.FS. This pairs well with build tools that generate .br and .gz files at deploy time (e.g. Vite, esbuild, or a post-build compression step).
Security ¶
OS files are served through celeris.Context.FileFromDir, which prevents directory traversal and symlink escape. For fs.FS files, paths are cleaned before opening.
Method Filtering ¶
Only GET and HEAD requests are processed. All other methods pass through to the next handler.
Ordering ¶
Place the static middleware after security and authentication middleware if you need to protect static files. Place it before compress and etag if you want framework-level caching/compression (though the static middleware sets its own ETag from file metadata, which the etag middleware will preserve).
Skipping ¶
Use Config.Skip for dynamic skip logic or Config.SkipPaths for exact-match path exclusions. Skipped requests call c.Next() without serving any files.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func New ¶
func New(config ...Config) celeris.HandlerFunc
New creates a static file middleware with the given config.
Example ¶
package main
import (
"github.com/goceleris/celeris/middleware/static"
)
func main() {
// Serve files from an OS directory.
_ = static.New(static.Config{
Root: "./public",
})
}
Output:
Example (Browse) ¶
package main
import (
"github.com/goceleris/celeris/middleware/static"
)
func main() {
// Enable directory listing.
_ = static.New(static.Config{
Root: "./public",
Browse: true,
})
}
Output:
Example (Compress) ¶
package main
import (
"time"
"github.com/goceleris/celeris/middleware/static"
)
func main() {
// Serve pre-compressed files when available. If the client sends
// Accept-Encoding: br, the middleware serves app.js.br instead of
// app.js (and sets Content-Encoding: br). Falls back to the original
// file when no pre-compressed variant exists.
_ = static.New(static.Config{
Root: "./dist",
Compress: true,
MaxAge: 7 * 24 * time.Hour,
})
}
Output:
Example (EmbedFS) ¶
package main
import (
"embed"
"github.com/goceleris/celeris/middleware/static"
)
//go:embed doc.go
var embeddedFS embed.FS
func main() {
// Serve files from an embedded filesystem.
_ = static.New(static.Config{
FS: embeddedFS,
Prefix: "/assets",
})
}
Output:
Example (MaxAge) ¶
package main
import (
"time"
"github.com/goceleris/celeris/middleware/static"
)
func main() {
// Set a 24-hour Cache-Control max-age alongside the automatic ETag
// and Last-Modified headers.
_ = static.New(static.Config{
Root: "./public",
MaxAge: 24 * time.Hour,
})
}
Output:
Example (Spa) ¶
package main
import (
"github.com/goceleris/celeris/middleware/static"
)
func main() {
// Serve a single-page application: non-existent paths fall back to
// index.html so the client-side router can handle them.
_ = static.New(static.Config{
Root: "./dist",
SPA: true,
})
}
Output:
Types ¶
type Config ¶
type Config struct {
// Skip defines a function to skip this middleware for certain requests.
Skip func(c *celeris.Context) bool
// SkipPaths lists paths to skip (exact match).
SkipPaths []string
// Root is the directory path on the OS filesystem from which to serve
// files. Mutually exclusive with FS. If neither Root nor FS is set,
// the middleware panics.
Root string
// FS is an [fs.FS] from which to serve files (e.g. embed.FS).
// Mutually exclusive with Root. If both Root and FS are set, FS takes
// precedence.
FS fs.FS
// Prefix is the URL path prefix to strip before looking up the file.
// For example, Prefix: "/static" serves /static/style.css from style.css
// in Root/FS. The prefix is matched at segment boundaries: /static does
// not match /static-docs.
// Default: "/" (serve from the URL root).
Prefix string
// Index is the default file to serve when the request path resolves to
// a directory.
// Default: "index.html".
Index string
// Browse enables directory listing when the request path resolves to
// a directory without an index file.
// Default: false.
Browse bool
// SPA enables single-page application mode. When true, requests for
// non-existent files serve the index file instead of falling through
// to the next handler. Useful for single-page applications.
// Default: false.
SPA bool
// MaxAge sets the Cache-Control max-age directive. Zero means no
// Cache-Control header.
MaxAge time.Duration
// Compress enables serving pre-compressed files. When true, the middleware
// checks for .br (Brotli) and .gz (gzip) variants of the requested file
// and serves them if the client accepts the encoding via Accept-Encoding.
// Brotli is preferred over gzip when both are accepted. Works with both
// OS filesystem (Root) and fs.FS.
Compress bool
}
Config defines the static file middleware configuration.