Skip to main content

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():

Serve directory with cache
  app.anyOf(
{Method.get, Method.head},
'/basic/**',
StaticHandler.directory(
staticDir,
cacheControl:
(final req, final fileInfo) => CacheControlHeader(maxAge: 86400),
).asHandler,
);

What this code does:

  1. HTTP Methods: anyOf({Method.get, Method.head}, ...) handles both GET and HEAD requests, which is standard for static file serving.
  2. Path Pattern: /basic/** uses a tail matching pattern where ** captures the remaining path segments to determine which file to serve.
  3. Static Handler: StaticHandler.directory() creates a handler that serves files from the specified directory with automatic MIME type detection.
  4. 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.txthttp://localhost:8080/basic/hello.txt
  • static_files/logo.svghttp://localhost:8080/basic/logo.svg

Single file serving

For serving individual files, use StaticHandler.file():

Serve single 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:

Short-term cache control
  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):

Long-term immutable caching
  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:

Cache busting config and usage
  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:

  1. Configure cache busting (see the CacheBustingConfig instantiation): CacheBustingConfig generates unique URLs based on file content hashes.
  2. Generate cache-busted URLs (see usage of buster.assetPath(...)): buster.assetPath('/static/hello.txt') returns something like /static/hello@6cb65f8d93fd9c4afe283092b9c3f74cafc04e33.txt.
  3. Serve with aggressive caching (see cache control settings for cache busted serving): Use long cache durations (1 year) with immutable: true because the URL changes when content changes.
  4. 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.

Directory paths

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

API documentation

Further reading