Static files
Static file serving is essential for web applications that need to deliver assets like images, CSS, JavaScript, documents, or other resources. Relic provides a powerful StaticHandler that automatically handles MIME types, caching headers, security, and advanced features like cache busting.
Basic directory serving
To serve static files from a directory, use StaticHandler.directory():
app.anyOf(
{Method.get, Method.head},
'/basic/**',
StaticHandler.directory(
staticDir,
cacheControl:
(final req, final fileInfo) => CacheControlHeader(maxAge: 86400),
).asHandler,
);
What this code does:
- HTTP Methods:
anyOf({Method.get, Method.head}, ...)handles both GET and HEAD requests, which is standard for static file serving. - Path Pattern:
/basic/**uses a tail matching pattern where**captures the remaining path segments to determine which file to serve. - Static Handler:
StaticHandler.directory()creates a handler that serves files from the specified directory with automatic MIME type detection. - Cache Control: Sets a cache duration of 86400 seconds (1 day), instructing browsers and CDNs to cache the files.
This serves all files from the _static_files directory under /basic/ URLs with 1-day caching. For example:
static_files/hello.txt→http://localhost:8080/basic/hello.txtstatic_files/logo.svg→http://localhost:8080/basic/logo.svg
Single file serving
For serving individual files, use StaticHandler.file():
app.get(
'/logo.svg',
StaticHandler.file(
File('example/_static_files/logo.svg'),
cacheControl:
(final req, final fileInfo) => CacheControlHeader(maxAge: 3600),
).asHandler,
);
This is useful for specific files like logos, favicons, robots.txt, or other well-known resources.
Cache control strategies
Effective caching is crucial for static file performance. Relic provides flexible cache control options:
Short-term caching
For files that might change frequently:
app.anyOf(
{Method.get, Method.head},
'/short-cache/**',
StaticHandler.directory(
staticDir,
cacheControl:
(final req, final fileInfo) => CacheControlHeader(
// Cache for 1 hour.
maxAge: 3600,
// Enable CDN and proxy caching.
publicCache: true,
),
).asHandler,
);
Long-term caching with immutable assets
For assets that never change (like versioned files):
app.anyOf(
{Method.get, Method.head},
'/long-cache/**',
StaticHandler.directory(
staticDir,
cacheControl:
(final req, final fileInfo) => CacheControlHeader(
// Cache for 1 year.
maxAge: 31536000,
publicCache: true,
// Tell browsers never to revalidate.
immutable: true,
),
).asHandler,
);
Cache busting
Cache busting ensures browsers fetch updated files when your assets change. Relic provides built-in cache busting support:
final buster = CacheBustingConfig(
mountPrefix: '/static',
fileSystemRoot: staticDir,
);
// Create an index page that demonstrates cache-busted URLs.
app.get(
'/',
respondWith((final _) async {
final helloUrl = await buster.assetPath('/static/hello.txt');
final logoUrl = await buster.assetPath('/static/logo.svg');
final html =
'<html><body>'
'<h1>Static files with cache busting</h1>'
'<ul>'
'<li><a href="$helloUrl">hello.txt</a></li>'
'<li><img src="$logoUrl" alt="logo" height="64" /></li>'
'</ul>'
'</body></html>';
return Response.ok(body: Body.fromString(html, mimeType: MimeType.html));
}),
);
// Serve static files with automatic cache busting.
app.anyOf(
{Method.get, Method.head},
'/static/**',
StaticHandler.directory(
staticDir,
cacheControl:
(final req, final fileInfo) => CacheControlHeader(
// Safe to cache long term with versioning.
maxAge: 31536000,
publicCache: true,
immutable: true,
),
cacheBustingConfig: buster,
).asHandler,
);
How cache busting works:
- Configure cache busting (see the
CacheBustingConfiginstantiation):CacheBustingConfiggenerates unique URLs based on file content hashes. - Generate cache-busted URLs (see usage of
buster.assetPath(...)):buster.assetPath('/static/hello.txt')returns something like/static/hello@6cb65f8d93fd9c4afe283092b9c3f74cafc04e33.txt. - Serve with aggressive caching (see cache control settings for cache busted serving): Use long cache durations (1 year) with
immutable: truebecause the URL changes when content changes. - When the file changes, the hash changes, forcing browsers to fetch the new version.
Security considerations
StaticHandler includes built-in security features:
- Path traversal protection: Prevents access to files outside the specified directory.
- Hidden file protection: Blocks access to files starting with
.(like.env,.git). - Symbolic link handling: Safely resolves symbolic links within the allowed directory.
These protections are automatically applied and ensure that your static file handler only serves files from the intended directory.
When serving static files from a directory, always use a tail matching path pattern (/**) to capture all files and subdirectories. The tail portion (/**) is used to determine the file path within the directory. Without it, the handler won't know which file to serve.
For single file serving with StaticHandler.file(), you don't need the tail pattern, but it can be useful for SPAs or other routing scenarios.
Examples & further reading
Examples
- Static files example - Complete example with cache busting.
API documentation
- StaticHandler class - Static file serving handler.
- CacheBustingConfig class - Cache busting configuration.
- CacheControlHeader class - Cache control header handling.
Further reading
- HTTP caching - Mozilla documentation on HTTP caching.
- Cache-Control header - Mozilla documentation on Cache-Control.
- ETag header - Mozilla documentation on ETags.