Static files
Static assets like images, CSS, and JavaScript files are essential for web
applications. The StaticRoute.directory() method makes it easy to serve entire
directories with automatic content-type detection for common file formats.
Serving static files
The simplest way to serve static files is to use StaticRoute.directory():
final staticDir = Directory('web/static');
pod.webServer.addRoute(
StaticRoute.directory(staticDir),
'/static/**',
);
This serves all files from the web/static directory at the /static path.
For example, web/static/logo.png becomes accessible at /static/logo.png.
The /** tail-match wildcard is required for serving directories. It matches all
paths under the prefix, allowing StaticRoute to map URLs to file system paths.
See Routing for more on wildcards.
Cache control
Control how browsers and CDNs cache your static files using the
cacheControlFactory parameter:
pod.webServer.addRoute(
StaticRoute.directory(
staticDir,
cacheControlFactory: StaticRoute.publicImmutable(maxAge: const Duration(minutes: 5)),
),
'/static/**',
);
Static file cache-busting
When deploying static assets, browsers and CDNs (like CloudFront) cache files aggressively for performance. This means updated files may not be served to users unless you implement a cache-busting strategy.
Serverpod provides CacheBustingConfig to automatically version your static
files. For more details, see the Relic documentation.
final staticDir = Directory('web/static');
final cacheBustingConfig = CacheBustingConfig(
mountPrefix: '/static',
fileSystemRoot: staticDir,
separator: '@', // or use custom separator like '___'
);
pod.webServer.addRoute(
StaticRoute.directory(
staticDir,
cacheBustingConfig: cacheBustingConfig,
cacheControlFactory: StaticRoute.publicImmutable(maxAge: const Duration(minutes: 5)),
),
'/static/**',
);
Generating versioned urls
Use the assetPath() method to generate cache-busted URLs for your assets:
// In your route handler
final imageUrl = await cacheBustingConfig.assetPath('/static/logo.png');
// Returns: /static/logo@<hash>.png
// Pass to your template
return MyPageWidget(logoUrl: imageUrl);
The cache-busting system:
- Automatically generates content-based hashes for asset versioning
- Allows custom separators (default
@, but you can use___or any other) - Preserves file extensions
- Works transparently - requesting
/static/logo@abc123.pngserves/static/logo.png
Conditional requests (Etags and Last-Modified)
StaticRoute automatically supports HTTP conditional requests through Relic's
StaticHandler. This provides efficient caching without transferring file
content when unchanged:
Supported features:
- ETag headers - Content-based fingerprinting for cache validation
- Last-Modified headers - Timestamp-based cache validation
- If-None-Match - Client sends ETag, server returns 304 Not Modified if unchanged
- If-Modified-Since - Client sends timestamp, server returns 304 if not modified
These work automatically without configuration:
Initial request:
GET /static/logo.png HTTP/1.1
Host: example.com
HTTP/1.1 200 OK
ETag: "abc123"
Last-Modified: Tue, 15 Nov 2024 12:00:00 GMT
Content-Length: 12345
[file content]
Subsequent request with ETag:
GET /static/logo.png HTTP/1.1
Host: example.com
If-None-Match: "abc123"
HTTP/1.1 304 Not Modified
ETag: "abc123"
[no body - saves bandwidth]
When combined with cache-busting, conditional requests provide a fallback validation mechanism even for cached assets, ensuring efficient delivery while maintaining correctness.
For dynamic content that changes per request, see Server-side HTML.