Skip to content

τjs Configuration

Complete reference for configuring τjs applications.

τ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.

taujs.config.ts
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 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;
};

Control where and how τjs runs.

export default defineConfig({
server: {
host: "localhost",
port: 5173,
hmrPort: 5174,
},
});

Values are resolved in this order (highest precedence first):

  1. CLI flags: --host, --port, --hmr-port
Terminal window
npm run dev -- --host 0.0.0.0 --port 3000
  1. Environment variables:

    • HOST or FASTIFY_ADDRESS
    • PORT or FASTIFY_PORT
    • HMR_PORT
  2. Config object: server.* properties

  3. Defaults: { host: 'localhost', port: 5173, hmrPort: 5174 }

server: {
host: "localhost"; // Loopback only (not accessible from network)
host: "0.0.0.0"; // All interfaces (accessible from network)
}

CLI shorthand:

Terminal window
npm run dev -- --host # Automatically becomes 0.0.0.0

Define frontend applications with their entry points and routes.

apps: [
{
appId: "web",
entryPoint: "client",
routes: [
/* ... */
],
plugins: [
/* optional Vite plugins */
],
},
{
appId: "admin",
entryPoint: "admin",
routes: [
/* ... */
],
},
];
PropertyTypeRequiredDescription
appIdstringYesUnique identifier for this app
entryPointstringYesDirectory under client root
routesAppRoute[]NoRoute definitions
pluginsPluginOption[]NoVite plugins for this app

Each entryPoint directory must contain:

client/{entryPoint}/
├── index.html # HTML template
├── entry-client.tsx # Client hydration entry
└── entry-server.tsx # SSR render entry

Routes define URL patterns, rendering strategies, and data requirements.

{
path: '/about',
attr: {
render: 'ssr'
}
}
{
path: '/users/:id',
attr: {
render: 'ssr',
data: async (params) => ({
userId: params.id
})
}
}
PropertyTypeRequiredDescription
pathstringYesURL pattern (path-to-regexp)
attrRouteAttributesNoRendering and data config
PropertyTypeDefaultDescription
render'ssr' | 'streaming'RequiredRendering strategy
hydratebooleantrueAdd React on client
metaRecord<string, unknown>{}Metadata for head
middlewareMiddlewareundefinedAuth and CSP
dataDataHandlerundefinedData loader

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

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 headContent runs
  • Requires meta property

SSR without client-side JavaScript:

{
path: '/terms',
attr: {
render: 'ssr',
hydrate: false
}
}
{
path: '/about',
attr: {
render: 'ssr',
data: async (params, ctx) => {
const res = await fetch('https://api.example.com/about');
return await res.json();
}
}
}
{
path: '/users/:id',
attr: {
render: 'ssr',
data: async (params) => ({
serviceName: 'UserService',
serviceMethod: 'getUser',
args: { id: params.id }
})
}
}

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) };
};
export default defineConfig({
security: {
csp: {
directives: {
"default-src": ["'self'"],
"script-src": ["'self'"],
"style-src": ["'self'", "'unsafe-inline'"],
"img-src": ["'self'", "data:", "https:"],
},
},
},
});
security: {
csp: {
directives: {
'default-src': ["'self'"],
'script-src': ["'self'"]
},
reporting: {
endpoint: '/api/csp-violations',
reportOnly: false,
onViolation: (report, req) => {
console.log('CSP violation:', report);
}
}
}
}
{
path: '/embed',
attr: {
render: 'ssr',
middleware: {
csp: {
mode: 'merge', // or 'replace'
directives: {
'frame-ancestors': ["'self'", 'https://trusted.com']
}
}
}
}
}
{
path: '/user/:id',
attr: {
render: 'ssr',
middleware: {
csp: {
directives: ({ params }) => ({
'img-src': [
"'self'",
`https://cdn.example.com/users/${params.id}/`
]
})
}
}
}
}
// Hard disable - no header
{
path: '/legacy',
attr: {
middleware: {
csp: false
}
}
}
// Soft disable - use global only
{
path: '/report',
attr: {
middleware: {
csp: {
disabled: true
}
}
}
}
{
path: '/dashboard',
attr: {
render: 'ssr',
middleware: {
auth: {}
}
}
}
{
path: '/admin',
attr: {
render: 'ssr',
middleware: {
auth: {
roles: ['admin', 'superadmin']
}
}
}
}
{
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.

export default defineConfig({
server: {
port: 3000,
},
apps: [
{
appId: "web",
entryPoint: "client",
routes: [
{
path: "/",
attr: {
render: "ssr",
data: async () => ({
title: "Home",
content: "Welcome",
}),
},
},
],
},
],
});
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'"],
},
},
},
});

τ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 auth
ErrorCauseSolution
”At least one app must be configured”Empty apps arrayAdd at least one app
”Routes require auth but authenticate() missing”Auth routes without decoratorAdd authenticate() to Fastify
”Route path declared in multiple apps”Duplicate pathsUse unique paths per app
”Entry client file not found”Missing build artifactsRun npm run build
”meta required for streaming routes”Streaming without metaAdd meta: {} to route
// ✅ Good - type checking
export default defineConfig({
apps: [
/* ... */
],
});
// ❌ Bad - no type checking
export default {
apps: [
/* ... */
],
};
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],
},
],
});
// ✅ Good - testable, reusable
data: async (params) => ({
serviceName: "UserService",
serviceMethod: "getUser",
args: { id: params.id },
});
// ⚠️ Less ideal - mixed concerns
data: async (params) => {
const res = await fetch(`/api/users/${params.id}`);
return await res.json();
};
// ✅ Good - reliable SEO
{
path: '/blog/:slug',
attr: {
render: 'streaming',
meta: {
title: 'Blog Post',
description: 'Read our latest blog',
ogType: 'article'
}
}
}
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;
}
};
export default defineConfig({
server: {
host: process.env.HOST || "localhost",
port: parseInt(process.env.PORT || "5173"),
},
apps: [
{
appId: "web",
entryPoint: "client",
routes: [
/* ... */
],
},
],
});
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
},
},
},
});