Assets & production
Serve static files with Fast-Twitch middleware and configure production assets.
Serve files with middleware
Fast-Twitch provides fast-twitch.middlewares.file/wrap-file. Use it around your routes to serve files from a configured directory. The upload/static example combines this middleware with Integrant and multipart handling.
(ns application.server
(:require [fast-twitch.middlewares.file :as file]
[fast-twitch.routing :as routing]))
(def app
(-> (routing/routes routes not-found)
(file/wrap-file "static" {:url-root "static"})))
A request to /static/css/app.css reads static/css/app.css. Keep the file root and URL root in your application's configuration rather than duplicating paths across handlers.
Compose response middleware
Use Fast-Twitch's content-type middleware to infer MIME types, default-charset middleware for text responses, not-modified middleware for conditional requests, and head middleware for HEAD responses. Each has its own reference page:
Build browser assets separately
A browser build tool such as Vite can write hashed JavaScript and CSS into a dedicated output directory. Configure wrap-file to serve that directory and use the build manifest when generating HTML. Keep browser assets and generated server output separate from ClojureScript source.
Learn from the working example
The public repository's 04-file-upload-static-integrant-bulma example demonstrates static files, multipart form parameters and an Integrant component graph. Follow its README to build and run the example.
File wrapper arguments and options
wrap-file accepts [handler root-path] or [handler root-path options]. file-request accepts the request and the same root/options and resolves to a file response or nil. File requests are GET/HEAD. A missing file continues to the wrapped handler.
| Option | Behavior in the pinned implementation |
|---|---|
:url-root |
URL prefix stripped before resolving under root-path; use "static" for /static/... |
:index-files? |
Default true; try index.html when a runtime reports a directory |
:show-dotfiles? |
Default false; dotfile path segments are rejected unless enabled |
:headers |
Additional response headers as map or header strings |
:prefer-handler? |
When true, delegates directly to the handler; this implementation does not then retry file serving |
Paths are percent-decoded; invalid escapes, upward .. segments and NUL bytes fail resolution. Do not infer a symlink security policy beyond the implementation's path checks. Node/Deno use filesystem metadata/read APIs; Bun uses Bun.file. Worker Fetch environments do not acquire a filesystem API through this wrapper; use their asset binding/export setup instead.
A file response supplies Content-Length and Last-Modified when available. Add content-type/charset and conditional middleware as needed. HEAD returns no body. File-info and cache-policy are separate wrappers, not implicit full asset pipelines.
Verify the behavior
Use the complete upload/static example and request /static/readme.txt with GET and HEAD. Request a missing file and inspect the app's 404 fallback. Add a stylesheet beneath the configured root, rebuild and request its URL. An existing file does not mean its MIME type or caching policy matches your intended behavior: inspect the wire headers too.
Versioned assets versus mutable content
A year-long immutable policy is appropriate for content-addressed asset filenames; it is inappropriate for a mutable URL that must reflect updates. The framework cache-policy wrapper memoizes header policies, not article bodies. Native response/body streams are resources: do not place a once-consumable body in a reusable response cache.
Complete Celld Fetch build · Complete workerd build · Middleware settings.