Static Assets
How τjs handles static file serving in development and production.
Overview
Section titled “Overview”τjs can mount static asset middleware for you, but it does not bundle any static file plugin by default.
Instead, you provide one or more Fastify plugins (for example @fastify/static) via the staticAssets option on createServer. If you don’t provide staticAssets, τjs does no static file serving - it only reads manifests from your client build for SSR.
This keeps the core clean and lets you decide whether assets are served by:
- Fastify (via
@fastify/staticor a custom plugin) - a CDN (S3/CloudFront, etc.)
- a reverse proxy (Nginx, Apache, etc.)
Default Behaviour
Section titled “Default Behaviour”If you don’t configure static assets:
import { createServer } from "@taujs/server";
await createServer({ fastify, config, serviceRegistry, clientRoot: "./dist/client",});What happens:
- τjs initialises its SSR server
- It loads manifests and templates from
clientRoot - It does not register any static file middleware
Result: HTML rendering works, manifest lookups work, but Fastify will not serve JS/CSS/images for you. You must either:
- mount your own static middleware, or
- serve assets via CDN / reverse proxy.
This is intentional - τjs doesn’t hide a static dependency inside the core package.
Registering Static Assets with @fastify/static
Section titled “Registering Static Assets with @fastify/static”To let τjs mount static file serving for you, pass a staticAssets configuration. For a single mount using @fastify/static:
import fastifyStatic from "@fastify/static";import { createServer } from "@taujs/server";import path from "node:path";
const clientRoot = path.join(process.cwd(), "dist", "client");
await createServer({ fastify, config, serviceRegistry, clientRoot,
staticAssets: { plugin: fastifyStatic, options: { // τjs will default root to your client build, but you can override: root: clientRoot, prefix: "/", // serve assets under / index: false, wildcard: false, // any other @fastify/static options... }, },});Under the hood τjs normalises this into a list of mount entries and registers them on the Fastify instance.
Multiple Static Mounts
Section titled “Multiple Static Mounts”You can mount multiple static plugins or prefixes:
await createServer({ fastify, config, clientRoot,
staticAssets: [ { // App assets plugin: fastifyStatic, options: { root: clientRoot, prefix: "/app/", }, }, { // Admin assets plugin: fastifyStatic, options: { root: clientRoot, prefix: "/admin/", }, }, ],});τjs sorts entries by prefix depth, so more specific prefixes are registered first.
Disabling τjs Static Mounts
Section titled “Disabling τjs Static Mounts”If you’re serving assets from elsewhere (CDN, Nginx, etc.), you can explicitly disable τjs static mounting:
await createServer({ fastify, config, serviceRegistry, clientRoot: "./dist/client", staticAssets: false, // τjs will not register any static middleware});Use this when:
- Assets are served from a CDN
- A reverse proxy (Nginx/Apache) serves static files directly
- You mount all static plugins yourself outside of τjs
τjs still reads manifests from clientRoot for SSR, but never serves the files.
Public Directory
Section titled “Public Directory”τjs expects a project-level public/ directory for non-bundled assets:
project/├── public/│ ├── favicon.ico│ ├── robots.txt│ └── app/│ └── logo.svg└── client/ ├── app/ └── admin/During build:
- Client build: copies
public/contents intodist/client/ - SSR build: uses
publicDir: false(no extra copying)
Result after build:
dist/client/├── favicon.ico├── robots.txt├── app/│ ├── logo.svg│ └── assets/└── admin/ └── assets/These files are then served by whatever static setup you’ve chosen (Fastify, CDN, proxy).
App-Specific Assets
Section titled “App-Specific Assets”You can namespace assets per app:
public/├── app/│ ├── logo.svg│ └── favicon.ico└── admin/ ├── logo.svg └── favicon.icoReferences in HTML:
<!-- Customer app --><img src="/app/logo.svg" />
<!-- Admin app --><img src="/admin/logo.svg" />As long as your static middleware (or CDN/proxy) serves dist/client/ at /, these URLs resolve correctly.
CDN Integration
Section titled “CDN Integration”Upload to CDN
Section titled “Upload to CDN”After building the client:
npm run build:client
# Upload to S3 (example)aws s3 sync dist/client/ s3://my-bucket/assets/ \ --cache-control "max-age=31536000,immutable" \ --exclude "*.html"
# HTML with no-cacheaws s3 sync dist/client/ s3://my-bucket/assets/ \ --cache-control "no-cache" \ --include "*.html"Disable τjs Static Serving
Section titled “Disable τjs Static Serving”await createServer({ fastify, config, serviceRegistry, clientRoot: "./dist/client", // still needed for manifests staticAssets: false, // CDN serves all assets});τjs uses the manifests for SSR, but all files are actually served by the CDN.
Cache Headers (Typical Strategy)
Section titled “Cache Headers (Typical Strategy)”- Hashed assets:
max-age=31536000, immutable - HTML files:
no-cache - Other files:
no-cache(unless they’re hashed and safe to cache long-term)
Reverse Proxy Setup
Section titled “Reverse Proxy Setup”Nginx Example
Section titled “Nginx Example”upstream nodejs { server localhost:3000;}
server { listen 80;
# Static assets - Nginx serves directly location ~ ^/.+/assets/ { root /app/dist/client; add_header Cache-Control "public, max-age=31536000, immutable"; }
# Try static first, then proxy to Node location / { root /app/dist/client; try_files $uri @nodejs; }
location @nodejs { proxy_pass http://nodejs; proxy_set_header Host $host; }}Configure τjs:
// Nginx handles static files; τjs only does SSRawait createServer({ fastify, config, serviceRegistry, clientRoot: "./dist/client", staticAssets: false,});Troubleshooting
Section titled “Troubleshooting”Assets Not Loading
Section titled “Assets Not Loading”Check one of the following is true:
- You configured
staticAssetswith a valid plugin (e.g.@fastify/static), or - You have a CDN / proxy correctly pointing at your built
dist/client/directory.
Verify the files actually exist:
ls dist/client/app/assets/# Should show built JS/CSS chunksWrong MIME Types
Section titled “Wrong MIME Types”If your static plugin needs MIME overrides, configure them in the plugin options:
import fastifyStatic from "@fastify/static";
await createServer({ fastify, config, clientRoot, staticAssets: { plugin: fastifyStatic, options: { root: "./dist/client", setHeaders: (res, filePath) => { if (filePath.endsWith(".js")) { res.setHeader("Content-Type", "application/javascript"); } }, }, },});