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.
StaticRoute.directory() automatically handles tail matching, so you don't need
to add ** to the path. The route will serve all files under the given prefix.
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.