Configuration & Plugins
Customize your Dinou application with dinou.config.js, enable global Partial Prerendering (PPR), mount low-level Node.js servers and WebSockets with configureServer, inject compile-time polyfills with setupFiles, and intercept HTTP requests using Web Standards plugins.
1. Configuration & Plugin System
In Dinou, the configuration and extensibility architecture is designed around two complementary layers:
- Universal Web Standards Layer (
onRequest): Operates across all supported runtimes (Node.js, Bun, Deno, and Cloudflare Workers) using the standard Fetch APIRequestandResponseinterfaces. - Low-Level Node.js Server Hook (
configureServer): Gives you direct, non-ejected access to the underlyinghttp.Server, allowing you to attach real-time WebSocket servers (such as Socket.IO orws) and register Node.js HTTP middlewares(req, res, next).
Request & Response APIs. Universal execution across Node, Bun, Deno, and Cloudflare Workers.configureServer grants direct access to http.Server for WebSockets and Node.js HTTP middlewares (req, res, next) without ejecting.setupFiles injected at Line 1 via Esbuild inject across all engine passes.2. The dinou.config.js File
Dinou automatically discovers your configuration file at the root of your project. The canonical format is ES Modules (dinou.config.js or dinou.config.ts) using the defineConfig helper:
// dinou.config.js
import { defineConfig } from "dinou";
// Optional: import storage adapters if running custom ISR persistence
// import { RedisStorage } from "dinou/server";
export default defineConfig({
// Global Partial Prerendering default (can be overridden per route)
ppr: false,
// React StrictMode in development (defaults to true)
reactStrictMode: true,
// Pluggable ISR / cache slot storage adapter (defaults to FileSystemStorage in Node/Bun)
// storage: new RedisStorage(redisClient),
// Compile-time setup scripts injected at Line 1 across all target runtimes
setupFiles: ["./src/polyfills.ts"],
// Array of Dinou plugins
plugins: [],
});2.1 Esbuild Pre-Bundling & Native Path Aliases
When Dinou loads dinou.config.js or dinou.config.ts, it automatically bundles the configuration file on the fly using Esbuild (<20ms) before execution:
- Native
tsconfig.jsonPath Aliases: You can freely import shared models, utilities, or plugin factories using path aliases (e.g.import { authPlugin } from "@/server/plugins/auth";) without encountering Node's runtimeERR_MODULE_NOT_FOUND. - First-Class TypeScript Support: You can author your configuration in
dinou.config.tswithout installing extra transpilers or ts-node runners. - External Dependencies: All third-party packages (such as
ioredis,socket.io,stripe) remain clean external references.
3. Top-Level Configuration Options
The DinouConfig schema provides top-level controls for runtime behavior and compilation:
3.1 Global Partial Prerendering (ppr)
Dinou introduces first-class Partial Prerendering (PPR). When ppr: true is configured globally in dinou.config.js:
- All static-eligible routes pre-render an instant static HTML shell at build/ISR time.
- Dynamic components wrapped in React
<Suspense>leave streaming placeholder holes that resume and stream from the server upon request.
// dinou.config.js
export default defineConfig({
ppr: true, // Enables Partial Prerendering across all eligible routes
});You can override PPR on a per-route or per-layout basis inside page_functions.ts or layout_functions.ts by exporting export const ppr = false;.
3.2 React StrictMode (reactStrictMode)
In Dinou, reactStrictMode defaults to true. When active in development mode, React intentionally invokes components and effects twice to identify unintended side effects and ensure strict compliance with the React Compiler.
// dinou.config.js
export default defineConfig({
// Enabled by default (recommended). Set to false to disable:
reactStrictMode: false,
});3.3 Storage Adapter (storage)
Dinou's ISR and ISG caching engine uses a pluggable storage adapter. By default, Dinou uses the local filesystem (in Node.js/Bun) or Deno KV (in Deno). You can supply a custom storage adapter (such as Redis or Cloudflare KV) in dinou.config.js:
// dinou.config.js
import { RedisStorage } from "dinou/server";
import Redis from "ioredis";
export default defineConfig({
storage: new RedisStorage(new Redis(process.env.REDIS_URL)),
});3.4 Universal Setup Files (setupFiles)
An array of file paths to execute before any application code runs. Injected at Line 1 of every compilation stage across all target runtimes via Esbuild's native inject mechanism.
// dinou.config.js
export default defineConfig({
// Global polyfills or early APM initialization (e.g. Sentry, Datadog)
setupFiles: ["./src/polyfills.ts"],
});3.5 Plugins Array (plugins)
An array of plugin objects conforming to the DinouPlugin interface, executed sequentially on every incoming request.
4. Dinou Plugin Architecture
In Dinou, a plugin is an object containing an optional name and hooks for both low-level Node server configuration and universal Web Standards request interception:
export interface DinouPlugin {
/** Descriptive identifier used in logging and fault attribution */
name?: string;
/** Compile-time setup scripts injected at Line 1 across all target bundles */
setupFiles?: string[];
/**
* Low-level Node.js HTTP/HTTPS server configuration hook.
* Runs in Node.js and Bun environments before server.listen().
*/
configureServer?(context: ServerHookContext): void | Promise<void>;
/**
* Universal Web Standards request interceptor.
* Runs on Node.js, Bun, Deno, and Cloudflare Workers on every incoming HTTP request.
*/
onRequest?(
request: Request,
context: RequestContextStore
): Promise<Response | void> | Response | void;
}5. Low-Level Node Hook: configureServer
Prior to this hook, applications requiring raw access to Node.js HTTP primitives (attaching WebSockets, integrating standard (req, res, next) middlewares like cors or helmet, or handling sub-millisecond health probes) were forced to eject the framework.
The configureServer plugin hook solves this problem completely. It gives you direct access to the Node.js HTTP/HTTPS server instance and the middleware pipeline in both Development (dinou dev) and Production (dinou start or standalone compiled binaries).
5.1 Context Parameters: { server, isDev, use }
The hook receives a single context object:
export interface ServerHookContext {
/** The native Node.js HTTP/HTTPS server instance */
server: http.Server | https.Server;
/** Boolean indicating whether the server is running in development mode (dinou dev) */
isDev: boolean;
/** Registers a low-level Node.js HTTP middleware (req, res, next) before Dinou route handlers */
use: (middleware: NodeMiddleware) => void;
}server
Bind WebSocket engines (new Server(server)), listen to upgrade events, or configure socket timeouts.
isDev
Toggle dev-only logging, configure permissive CORS during local testing, or enable development inspect tools.
use(fn)
Register classic (req, res, next) middlewares for instant sub-millisecond probes or third-party packages.
5.2 The Node Middleware Pipeline (use)
Middlewares registered via use adopt the standard Node.js middleware signature (req, res, next) (compatible with utilities like cors or helmet):
http.createServer() runner and does not include or depend on Express. The use helper accepts functions operating directly on native Node IncomingMessage and ServerResponse streams.type NodeMiddleware = (
req: http.IncomingMessage,
res: http.ServerResponse,
next: () => void
) => void | Promise<void>;When a request arrives at the Node server, Dinou passes req and res through each registered middleware. The middleware can:
- Call
next(): Hand off control to the next middleware in line, and ultimately to Dinou's page routing and React SSR engine. - Omit calling
next()(Short-Circuit): If an external library (like Socket.IO) handles the HTTP request, return without callingnext(). Dinou bypasses page routing and will not produce a 404 error. - Send Response via
res.end(): For Kubernetes health checks (/healthz) or Prometheus metrics (/metrics), write headers and send the response directly. Dinou detects that headers were sent and halts the pipeline immediately with zero rendering overhead.
5.3 configureServer vs. onRequest Decision Matrix
Choose the appropriate plugin hook based on the runtime requirements of your task:
| Feature | configureServer({ use }) | onRequest(request, context) |
|---|---|---|
| Target Runtime | Node.js & Bun | Universal (Node, Bun, Deno, Cloudflare) |
| API Standard | Low-Level Node (IncomingMessage, ServerResponse) | Web Standards Fetch API (Request, Response) |
| Execution Point | At the Node HTTP server entry, before Dinou router | Inside Dinou pipeline, before route rendering |
| WebSockets & TCP | Yes (Socket.IO, ws, upgrade events) | No (HTTP request/response only) |
| Server Context | Can mutate req | Attaches to context (for getContext()) |
| Best Used For | WebSockets, (req, res, next) middlewares (cors, helmet), /healthz probes, raw body buffers | Auth redirects, i18n routing, custom response headers, RSC context enrichment |
6. Web Standards Hook: onRequest
The onRequest hook receives the standard Web Request object and Dinou's mutable RequestContextStore.
6.1 The context Object (RequestContextStore)
The context parameter provides access to parsed request metadata, response mutation helpers, platform edge bindings, and custom shared state:
export interface RequestContextStore {
/** Parsed incoming request metadata */
req: {
path: string; // Normalized URL pathname (e.g. "/admin/settings")
method: string; // HTTP method ("GET", "POST", etc.)
cookies: Record<string, string>; // Parsed cookie key-value pairs
headers: Record<string, string>; // Lowercased incoming HTTP headers
query: Record<string, string>; // Parsed URL search parameters
env?: Record<string, any>; // Platform environment bindings
ctx?: any; // Platform execution context
};
/** Outgoing response mutation helpers */
res: {
cookie(name: string, value: string, options?: CookieOptions): void;
clearCookie(name: string, options?: CookieOptions): void;
setHeader(name: string, value: string): void;
status(code: number): void;
redirect(url: string): void;
redirect(status: number, url: string): void;
};
/** Direct platform bindings (Cloudflare Workers KV, D1, R2, etc.) */
env?: Record<string, any>;
/** Platform runtime context (e.g. ctx.waitUntil for background execution) */
ctx?: { waitUntil(promise: Promise<any>): void };
/** Custom state attached by plugins (context.user, context.session, etc.) */
[key: string]: any;
}| Property | Type | Description |
|---|---|---|
| context.req | Object | Parsed request helpers: cookies, headers, query, path, and method. |
| context.res | ResponseProxy | Response mutation helpers: cookie(), clearCookie(), setHeader(), status(), and redirect(). |
| context.env | Record<string, any> | Direct platform bindings in serverless/edge runtimes (e.g. Cloudflare KV, D1 databases, R2 buckets). |
| context.ctx | ExecutionContext | Platform execution context, allowing ctx.waitUntil(promise) for non-blocking background tasks without delaying the HTTP response. |
| context[key] | any | Any custom property attached directly to context (e.g. context.user, context.session) is preserved in AsyncLocalStorage and accessible via getContext(). |
6.2 Pattern 1: Short-Circuit & Webhooks (Returning Response)
If onRequest returns a Web Response instance, Dinou immediately returns that response to the client and aborts further processing. React Server Component rendering is completely bypassed:
const apiProbePlugin = {
name: "api-probe",
onRequest(request) {
const url = new URL(request.url);
if (url.pathname === "/api/ping") {
return Response.json({ status: "pong", timestamp: Date.now() });
}
},
};6.3 Pattern 2: Middleware & Context Mutation (getContext)
If onRequest returns void (or undefined), request processing continues normally. Any property attached to context is preserved in AsyncLocalStorage and becomes accessible in any server-side module:
const userSessionPlugin = {
name: "user-session",
async onRequest(request, context) {
const sessionCookie = context.req.cookies["session_id"];
if (sessionCookie) {
// Validate session and attach user object
const user = await validateSession(sessionCookie);
context.user = user;
}
},
};6.4 Context Sharing with Server Functions
When a client invokes a Server Function, Dinou:
- Executes all registered plugin
onRequesthooks for the incoming RPC request. - Instantiates the isolated Server Function execution context.
- Automatically copies all custom properties from the root context to the action context.
This means any authentication claims, multi-tenant IDs, or shared services attached during onRequest are immediately accessible inside "use server" actions via getContext():
// src/actions/update-profile.ts
"use server";
import { getContext } from "dinou";
export async function updateProfile(formData: FormData) {
const { user } = getContext();
if (!user) {
throw new Error("Unauthorized");
}
// user is cryptographically verified from the plugin onRequest hook
await db.user.update({ where: { id: user.id }, data: { name: formData.get("name") } });
}7. Deep Dive: Execution Points (configureServer vs. onRequest)
Understanding the fundamental distinction between one-time server startup and per-request lifecycles.
| Plugin Hook | Execution Phase | Frequency | Scope & Inputs | Primary Purpose |
|---|---|---|---|---|
| configureServer | Server Startup / Bootstrap | 1 time (on boot) | http.Server, isDev, use(fn) | Configure server infrastructure (WebSockets, socket listeners, timeouts, Node middleware queue). |
| onRequest | Per-Request Runtime | N times (every request) | Web Standards Request, RequestContextStore | Handle incoming user traffic (cookies, auth checks, 302/401 short-circuit redirects, context mutation). |
Chronological Execution Flow: From Startup to Page Render
- Process Boot: Command
dinou startordinou devinitializes Node's nativehttp.createServer(). - configureServer Hook Invoked: Dinou executes all plugin
configureServerhooks beforeserver.listen().- WebSocket engines (Socket.IO,
ws) bind toserver. - Redis/Valkey pub/sub adapters establish connection.
- Node middlewares register in the internal pipeline queue via
use((req, res, next) => ...).
- WebSocket engines (Socket.IO,
- Port Listening: The server starts accepting connections via
server.listen(port).
- HTTP Hit: A client makes a request (e.g.
GET /dashboard). - Node Middlewares Execute: Registered
usefunctions run. If handling/healthzor/socket.io, they respond viares.end()and bypass Dinou completely. Otherwise, they invokenext(). - Web Standards Conversion: Dinou constructs standard
Requestandcontextobjects. - onRequest Hooks Execute: Plugins run sequentially. If returning a
Response, Dinou terminates early. If mutatingcontext, data is preserved forgetContext(). - Page & RSC Rendering: Dinou resolves route contracts and renders React Server Components.
8. Compile-Time Setup Files (setupFiles)
In standard ECMAScript Modules (ESM), all static import statements are resolved and executed before module bodies evaluate. When third-party packages (such as pdfjs-dist or telemetry agents like Sentry and Datadog) expect globals on globalThis, attempting to assign polyfills inside application components or dinou.config.js runs too late and results in runtime ReferenceError exceptions.
Dinou's universal Setup Files architecture guarantees that your polyfill or instrumentation scripts execute at Line 1 of every engine bundle across all environments.
8.1 Resolution & Precedence Hierarchy
Dinou resolves setup files using an unambiguous hierarchy:
- 1. Plugin Setup Files (
plugin.setupFiles: string[]): Run first. Allows infrastructure plugins (APM, distributed tracing, metrics) to initialize before user code. - 2. Explicit User Config (
config.setupFiles: string[]): Defined indinou.config.js. When specified, this completely replaces any zero-config convention files. - 3. Zero-Config Convention: If
config.setupFilesis omitted, Dinou automatically checks for./dinou.setup.jsor./dinou.setup.tsstrictly in the project root directory.
8.2 Environment Polyfills Recipe (DOMMatrix / Geometry)
To polyfill W3C geometry interfaces (required by pdfjs-dist) in headless Node, Bun, and standalone compiled binaries without native C++ compilation:
// dinou.setup.js (Project Root)
import { DOMMatrix, DOMPoint, DOMRect } from "@napi-rs/canvas/geometry.js";
// 1. W3C Geometry Interfaces (Pure JS, zero C++ native compilation bindings)
if (typeof globalThis.DOMMatrix === "undefined") {
globalThis.DOMMatrix = DOMMatrix;
globalThis.DOMPoint = DOMPoint;
globalThis.DOMRect = DOMRect;
}
// 2. Minimal stubs for headless server environments
if (typeof globalThis.ImageData === "undefined") {
globalThis.ImageData = class ImageData {
constructor(width, height) {
this.width = width;
this.height = height;
this.data = new Uint8ClampedArray(width * height * 4);
}
};
}9. Real-World Plugin Examples (Socket.IO & Redis)
The following complete production example demonstrates combining configureServer for real-time WebSockets and Redis clustering with onRequest for authentication guards and webhooks:
// dinou.config.js
import { defineConfig } from "dinou";
import { socketServerPlugin } from "./src/plugins/socket-server.js";
import { authGuardPlugin } from "./src/plugins/auth-guard.js";
import { stripeWebhookPlugin } from "./src/plugins/stripe-webhook.js";
export default defineConfig({
ppr: true,
reactStrictMode: true,
plugins: [
// 1. WebSocket Engine & Node Middlewares
socketServerPlugin(),
// 2. Short-Circuit Webhook Interceptor
stripeWebhookPlugin(),
// 3. User Session & Route Authentication Guard
authGuardPlugin(),
],
});// src/plugins/socket-server.js
import { Server as SocketIOServer } from "socket.io";
import { createClient } from "redis";
import { createAdapter } from "@socket.io/redis-adapter";
export function socketServerPlugin() {
return {
name: "socket-io-redis",
async configureServer({ server, isDev, use }) {
// 1. Sub-millisecond Kubernetes health probe (bypasses Dinou SSR)
use((req, res, next) => {
if (req.url === "/healthz") {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("OK");
return;
}
next();
});
// 2. Intercept Socket.IO polling paths so Dinou doesn't return 404
use((req, res, next) => {
if (req.url && (req.url === "/socket.io" || req.url.startsWith("/socket.io/"))) {
return; // Socket.IO internal listener handles this
}
next();
});
// 3. Bind Socket.IO to the native Node HTTP Server
const io = new SocketIOServer(server, {
cors: { origin: isDev ? "*" : false },
});
// 4. Multi-instance horizontal scaling with Redis
const redisUrl = process.env.REDIS_URL;
if (redisUrl) {
const pubClient = createClient({ url: redisUrl });
const subClient = pubClient.duplicate();
await pubClient.connect();
await subClient.connect();
io.adapter(createAdapter(pubClient, subClient));
}
io.on("connection", (socket) => {
console.log("Client connected:", socket.id);
});
},
};
}// src/plugins/stripe-webhook.js
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export function stripeWebhookPlugin() {
return {
name: "stripe-webhook",
async onRequest(request) {
const url = new URL(request.url);
// Short-circuit: Handle raw webhook and bypass React SSR rendering completely
if (url.pathname === "/api/webhooks/stripe" && request.method === "POST") {
const sig = request.headers.get("stripe-signature");
const rawBody = await request.text();
try {
const event = stripe.webhooks.constructEvent(
rawBody,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
if (event.type === "checkout.session.completed") {
// Fulfill the customer's purchase in the database
}
return Response.json({ received: true });
} catch (err) {
return new Response(`Webhook Error: ${err.message}`, { status: 400 });
}
}
},
};
}// src/plugins/auth-guard.js
export function authGuardPlugin() {
return {
name: "auth-guard",
async onRequest(request, context) {
const url = new URL(request.url);
// Protect /dashboard routes
if (url.pathname.startsWith("/dashboard")) {
const token = context.req.cookies["auth_token"];
if (!token) {
return Response.redirect(new URL("/login", request.url), 302);
}
const user = await verifyToken(token);
if (!user) {
return Response.redirect(new URL("/login", request.url), 302);
}
// Attach verified user to context for getContext() in RSC & Server Functions
context.user = user;
}
},
};
}10. Registration & Execution Pipeline Order
Plugins declared in the plugins array of defineConfig execute sequentially according to their order:
- Strict Sequential Execution: Plugins execute top-to-bottom in the exact order declared (
plugins[0]→plugins[1]→plugins[2]). Dinou awaits each plugin'sonRequestpromise before evaluating the next. - Short-Circuit Precedence: If a plugin returns a
Responseinstance (e.g. returningResponse.json()from a webhook orResponse.redirect()from a guard), the pipeline terminates immediately. Subsequent plugins and React rendering are skipped. - Cumulative Context Sharing: Context mutations are cumulative. A downstream plugin receives the exact
contextobject enriched by upstream plugins.
Recommended Layering Order:
- Layer 1 (Node Server & WebSockets): Plugins declaring
configureServer(e.g. Socket.IO, Node HTTP middlewares). - Layer 2 (Public Webhooks & Probes): Request interceptors returning early
Response(e.g. Stripe webhooks, ping checks). - Layer 3 (Session & Tenant Resolution): Cookie decoders and auth token validators attaching
context.user. - Layer 4 (Route Guards & Access Control): Plugins verifying permissions and issuing 302 redirects.
11. Fault Isolation & Error Handling
Dinou provides built-in try-catch fault isolation for all active plugins:
name property. If an unhandled exception occurs inside onRequest, Dinou logs an informative error attributing the failure to that specific plugin name:[Dinou Plugin Error: "my-plugin"]: TypeError ...500 Internal Server Error response, preventing server crash or zombie process states.