τjs Configuration
Complete reference for configuring τjs applications.
Overview
Section titled “Overview”τjs uses a declarative configuration file (taujs.config.ts) where you define:
- Server settings (host, port, HMR)
- Applications and their entry points
- Routes with rendering strategies
- Security policies (CSP, authentication)
- Data loading patterns
All configuration is validated at startup with helpful error messages.
Basic Configuration
Section titled “Basic Configuration”import { defineConfig } from "@taujs/server/config";
export default defineConfig({ server: { host: "localhost", port: 5173, hmrPort: 5174, }, apps: [ { appId: "web", entryPoint: "client", routes: [ { path: "/", attr: { render: "ssr", data: async () => ({ message: "Hello World" }), }, }, ], }, ],});Type Definitions
Section titled “Type Definitions”type TaujsConfig = { server?: ServerConfig; security?: SecurityConfig; apps: AppConfig[];};
type ServerConfig = { host?: string; // Default: 'localhost' port?: number; // Default: 5173 hmrPort?: number; // Default: 5174};
type AppConfig = { appId: string; entryPoint: string; plugins?: PluginOption[]; routes?: AppRoute[];};
type AppRoute = { path: string; attr?: RouteAttributes;};Server Configuration
Section titled “Server Configuration”Control where and how τjs runs.
export default defineConfig({ server: { host: "localhost", port: 5173, hmrPort: 5174, },});Configuration Precedence
Section titled “Configuration Precedence”Values are resolved in this order (highest precedence first):
- CLI flags:
--host,--port,--hmr-port
npm run dev -- --host 0.0.0.0 --port 3000-
Environment variables:
HOSTorFASTIFY_ADDRESSPORTorFASTIFY_PORTHMR_PORT
-
Config object:
server.*properties -
Defaults:
{ host: 'localhost', port: 5173, hmrPort: 5174 }
Host Values
Section titled “Host Values”server: { host: "localhost"; // Loopback only (not accessible from network) host: "0.0.0.0"; // All interfaces (accessible from network)}CLI shorthand:
npm run dev -- --host # Automatically becomes 0.0.0.0App Configuration
Section titled “App Configuration”Define frontend applications with their entry points and routes.
apps: [ { appId: "web", entryPoint: "client", routes: [ /* ... */ ], plugins: [ /* optional Vite plugins */ ], }, { appId: "admin", entryPoint: "admin", routes: [ /* ... */ ], },];App Properties
Section titled “App Properties”| Property | Type | Required | Description |
|---|---|---|---|
appId | string | Yes | Unique identifier for this app |
entryPoint | string | Yes | Directory under client root |
routes | AppRoute[] | No | Route definitions |
plugins | PluginOption[] | No | Vite plugins for this app |
Entry Point Structure
Section titled “Entry Point Structure”Each entryPoint directory must contain:
client/{entryPoint}/├── index.html # HTML template├── entry-client.tsx # Client hydration entry└── entry-server.tsx # SSR render entryRoute Configuration
Section titled “Route Configuration”Routes define URL patterns, rendering strategies, and data requirements.
Basic Route
Section titled “Basic Route”{ path: '/about', attr: { render: 'ssr' }}Route with Parameters
Section titled “Route with Parameters”{ path: '/users/:id', attr: { render: 'ssr', data: async (params) => ({ userId: params.id }) }}Route Properties
Section titled “Route Properties”| Property | Type | Required | Description |
|---|---|---|---|
path | string | Yes | URL pattern (path-to-regexp) |
attr | RouteAttributes | No | Rendering and data config |
Route Attributes
Section titled “Route Attributes”| Property | Type | Default | Description |
|---|---|---|---|
render | 'ssr' | 'streaming' | Required | Rendering strategy |
hydrate | boolean | true | Add React on client |
meta | Record<string, unknown> | {} | Metadata for head |
middleware | Middleware | undefined | Auth and CSP |
data | DataHandler | undefined | Data loader |
Rendering Strategies
Section titled “Rendering Strategies”SSR (Server-Side Rendering)
Section titled “SSR (Server-Side Rendering)”Complete HTML rendered before sending:
{ path: '/products', attr: { render: 'ssr', data: async () => { const products = await db.products.findAll(); return { products }; } }}Characteristics:
- Data fully loaded before rendering
- Complete HTML in single response
- Guaranteed data in
headContent
Streaming SSR
Section titled “Streaming SSR”Progressive HTML delivery:
{ path: '/dashboard', attr: { render: 'streaming', meta: { // Required for streaming title: 'Dashboard', description: 'User dashboard' }, data: async () => { const metrics = await fetchMetrics(); return { metrics }; } }}Characteristics:
- Shell sent immediately
- Content streams as it renders
- Data may not be ready when
headContentruns - Requires
metaproperty
Static (No Hydration)
Section titled “Static (No Hydration)”SSR without client-side JavaScript:
{ path: '/terms', attr: { render: 'ssr', hydrate: false }}Data Loading
Section titled “Data Loading”Direct Return
Section titled “Direct Return”{ path: '/about', attr: { render: 'ssr', data: async (params, ctx) => { const res = await fetch('https://api.example.com/about'); return await res.json(); } }}Service Descriptor
Section titled “Service Descriptor”{ path: '/users/:id', attr: { render: 'ssr', data: async (params) => ({ serviceName: 'UserService', serviceMethod: 'getUser', args: { id: params.id } }) }}Request Context
Section titled “Request Context”Data handlers receive context:
data: async (params, ctx) => { // ctx.traceId: Request trace ID // ctx.logger: Scoped logger // ctx.headers: Request headers
ctx.logger.info({ userId: params.id }, "Loading user");
return { user: await getUser(params.id) };};Security Configuration
Section titled “Security Configuration”Content Security Policy
Section titled “Content Security Policy”export default defineConfig({ security: { csp: { directives: { "default-src": ["'self'"], "script-src": ["'self'"], "style-src": ["'self'", "'unsafe-inline'"], "img-src": ["'self'", "data:", "https:"], }, }, },});CSP with Reporting
Section titled “CSP with Reporting”security: { csp: { directives: { 'default-src': ["'self'"], 'script-src': ["'self'"] }, reporting: { endpoint: '/api/csp-violations', reportOnly: false, onViolation: (report, req) => { console.log('CSP violation:', report); } } }}Per-Route CSP
Section titled “Per-Route CSP”{ path: '/embed', attr: { render: 'ssr', middleware: { csp: { mode: 'merge', // or 'replace' directives: { 'frame-ancestors': ["'self'", 'https://trusted.com'] } } } }}Dynamic CSP
Section titled “Dynamic CSP”{ path: '/user/:id', attr: { render: 'ssr', middleware: { csp: { directives: ({ params }) => ({ 'img-src': [ "'self'", `https://cdn.example.com/users/${params.id}/` ] }) } } }}Disabling CSP
Section titled “Disabling CSP”// Hard disable - no header{ path: '/legacy', attr: { middleware: { csp: false } }}
// Soft disable - use global only{ path: '/report', attr: { middleware: { csp: { disabled: true } } }}Authentication
Section titled “Authentication”Require Authentication
Section titled “Require Authentication”{ path: '/dashboard', attr: { render: 'ssr', middleware: { auth: {} } }}Role-Based Access
Section titled “Role-Based Access”{ path: '/admin', attr: { render: 'ssr', middleware: { auth: { roles: ['admin', 'superadmin'] } } }}Custom Auth Metadata
Section titled “Custom Auth Metadata”{ path: '/api/data', attr: { render: 'ssr', middleware: { auth: { strategy: 'api-key', redirect: '/login' } } }}Note: τjs doesn’t interpret roles, strategy, or redirect. These are metadata for your authenticate decorator to read.
Complete Examples
Section titled “Complete Examples”Single Page Application
Section titled “Single Page Application”export default defineConfig({ server: { port: 3000, }, apps: [ { appId: "web", entryPoint: "client", routes: [ { path: "/", attr: { render: "ssr", data: async () => ({ title: "Home", content: "Welcome", }), }, }, ], }, ],});Multi-App Configuration
Section titled “Multi-App Configuration”export default defineConfig({ server: { host: "localhost", port: 5173, }, apps: [ { appId: "customer", entryPoint: "app", routes: [ { path: "/app/:feature?/:id?", attr: { render: "streaming", meta: { title: "App" }, middleware: { auth: { strategy: "jwt" } }, }, }, ], }, { appId: "admin", entryPoint: "admin", routes: [ { path: "/admin/:section?/:id?", attr: { render: "ssr", middleware: { auth: { strategy: "session", roles: ["admin"], }, }, }, }, ], }, ], security: { csp: { directives: { "default-src": ["'self'"], "script-src": ["'self'"], "style-src": ["'self'", "'unsafe-inline'"], }, }, },});Validation
Section titled “Validation”τjs validates configuration at startup:
[τjs] [config] Loaded 2 app(s), 15 route(s) in 2.3ms[τjs] [security] CSP configured (15/15 routes) in 0.8ms[τjs] [auth] ✓ 5 route(s) require authCommon Errors
Section titled “Common Errors”| Error | Cause | Solution |
|---|---|---|
| ”At least one app must be configured” | Empty apps array | Add at least one app |
| ”Routes require auth but authenticate() missing” | Auth routes without decorator | Add authenticate() to Fastify |
| ”Route path declared in multiple apps” | Duplicate paths | Use unique paths per app |
| ”Entry client file not found” | Missing build artifacts | Run npm run build |
| ”meta required for streaming routes” | Streaming without meta | Add meta: {} to route |
Best Practices
Section titled “Best Practices”1. Use defineConfig
Section titled “1. Use defineConfig”// ✅ Good - type checkingexport default defineConfig({ apps: [ /* ... */ ],});
// ❌ Bad - no type checkingexport default { apps: [ /* ... */ ],};2. Group Routes by Feature
Section titled “2. Group Routes by Feature”const authRoutes: AppRoute[] = [ { path: "/login", attr: { render: "ssr" } }, { path: "/register", attr: { render: "ssr" } },];
const dashboardRoutes: AppRoute[] = [ { path: "/dashboard", attr: { render: "streaming", meta: {} } }, { path: "/settings", attr: { render: "ssr" } },];
export default defineConfig({ apps: [ { appId: "web", entryPoint: "client", routes: [...authRoutes, ...dashboardRoutes], }, ],});3. Use Service Descriptors
Section titled “3. Use Service Descriptors”// ✅ Good - testable, reusabledata: async (params) => ({ serviceName: "UserService", serviceMethod: "getUser", args: { id: params.id },});
// ⚠️ Less ideal - mixed concernsdata: async (params) => { const res = await fetch(`/api/users/${params.id}`); return await res.json();};4. Provide Complete Meta for Streaming
Section titled “4. Provide Complete Meta for Streaming”// ✅ Good - reliable SEO{ path: '/blog/:slug', attr: { render: 'streaming', meta: { title: 'Blog Post', description: 'Read our latest blog', ogType: 'article' } }}5. Use Structured Logging
Section titled “5. Use Structured Logging”data: async (params, ctx) => { ctx.logger.info({ userId: params.id }, "Loading user");
try { const user = await getUser(params.id); return { user }; } catch (err) { ctx.logger.error({ userId: params.id, error: err }, "Load failed"); throw err; }};Environment-Specific Configuration
Section titled “Environment-Specific Configuration”Using Environment Variables
Section titled “Using Environment Variables”export default defineConfig({ server: { host: process.env.HOST || "localhost", port: parseInt(process.env.PORT || "5173"), }, apps: [ { appId: "web", entryPoint: "client", routes: [ /* ... */ ], }, ],});Conditional Configuration
Section titled “Conditional Configuration”const isDev = process.env.NODE_ENV !== "production";
export default defineConfig({ server: { port: isDev ? 5173 : 3000, }, security: { csp: { directives: { "script-src": isDev ? ["'self'", "'unsafe-inline'"] // Dev only : ["'self'"], // Production }, }, },});