@device-router/middleware-koa
v0.4.0
Published
Koa middleware for DeviceRouter — device classification and rendering hints per request
Maintainers
Readme
@device-router/middleware-koa
Koa middleware for DeviceRouter. Adds device classification and rendering hints to every request.
Installation
pnpm add @device-router/middleware-koa @device-router/storageFor automatic probe injection:
pnpm add @device-router/probeQuick start
import Koa from 'koa';
import { createDeviceRouter } from '@device-router/middleware-koa';
import { MemoryStorageAdapter } from '@device-router/storage';
const app = new Koa();
const { middleware, probeEndpoint } = createDeviceRouter({
storage: new MemoryStorageAdapter(),
});
app.use(middleware);
app.use(async (ctx) => {
if (ctx.method === 'POST' && ctx.path === '/device-router/probe') {
return probeEndpoint(ctx);
}
const profile = ctx.state.deviceProfile;
if (profile?.hints.preferServerRendering) {
ctx.body = renderSSR();
} else if (profile?.hints.deferHeavyComponents) {
ctx.body = renderLite();
} else {
ctx.body = renderFull();
}
});
app.listen(3000);How it works
- Probe endpoint receives device signals from the browser and stores a classified profile
- Middleware reads the session cookie, loads the profile from storage, and attaches it to
ctx.state.deviceProfile - Your route handlers use
ctx.state.deviceProfile.hintsandctx.state.deviceProfile.tiersto adapt responses
Probe auto-injection
Automatically inject the probe <script> into HTML responses:
const { middleware, probeEndpoint, injectionMiddleware } = createDeviceRouter({
storage: new MemoryStorageAdapter(),
injectProbe: true,
probeNonce: 'my-csp-nonce', // optional
});
app.use(injectionMiddleware); // before routes
app.use(middleware);Streaming responses: Injection only runs when
ctx.bodyis a string. If you setctx.bodyto aStream, injection is silently skipped. Add the probe<script>tag to your HTML shell manually instead.
Custom thresholds
const { middleware, probeEndpoint } = createDeviceRouter({
storage,
thresholds: {
cpu: { lowUpperBound: 4, midUpperBound: 8 },
memory: { midUpperBound: 8 },
},
});Options
| Option | Type | Default | Description |
| --------------------- | -------------------------------------- | ----------------- | --------------------------------------------- |
| storage | StorageAdapter | (required) | Storage backend for profiles |
| cookieName | string | 'dr_session' | Session cookie name |
| cookiePath | string | '/' | Cookie path |
| cookieSecure | boolean | false | Set Secure flag on the session cookie |
| ttl | number | 86400 (24h) | Profile TTL in seconds |
| rejectBots | boolean | true | Reject bot/crawler probe submissions |
| thresholds | TierThresholds | Built-in defaults | Custom tier thresholds (validated at startup) |
| injectProbe | boolean | false | Auto-inject probe into HTML |
| probePath | string | — | Custom probe endpoint path |
| probeNonce | string \| ((ctx: Context) => string) | — | CSP nonce for injected script |
| fallbackProfile | FallbackProfile | — | Fallback profile for first requests |
| classifyFromHeaders | boolean | false | Classify from UA/Client Hints |
| onEvent | OnEventCallback | — | Observability callback for logging/metrics |
Observability
Pass an onEvent callback to receive events for classification, storage, bot rejection, and errors:
const { middleware, probeEndpoint } = createDeviceRouter({
storage,
onEvent: (event) => {
console.log(`[device-router] ${event.type}`, event);
},
});See the Observability guide for details.
Exports
createDeviceRouter(options)— All-in-one setup returning{ middleware, probeEndpoint, injectionMiddleware? }createMiddleware(options)— Standalone middlewarecreateProbeEndpoint(options)— Standalone probe endpoint handlercreateInjectionMiddleware(options)— Standalone injection middleware
Compatibility
- Koa 2.x and 3.x
- Node.js >= 20
License
MIT
