@device-router/middleware-fastify
v0.4.0
Published
Fastify plugin for DeviceRouter — device classification and rendering hints per request
Maintainers
Readme
@device-router/middleware-fastify
Fastify middleware for DeviceRouter. Adds device classification and rendering hints to every request.
Installation
pnpm add @device-router/middleware-fastify @device-router/storage @fastify/cookieFor automatic probe injection:
pnpm add @device-router/probeQuick start
import Fastify from 'fastify';
import cookie from '@fastify/cookie';
import { createDeviceRouter } from '@device-router/middleware-fastify';
import { MemoryStorageAdapter } from '@device-router/storage';
const app = Fastify();
const { middleware, probeEndpoint } = createDeviceRouter({
storage: new MemoryStorageAdapter(),
});
await app.register(cookie);
await app.register(middleware);
app.post('/device-router/probe', probeEndpoint);
app.get('/', (req, reply) => {
const profile = req.deviceProfile;
if (profile?.hints.preferServerRendering) {
return reply.send(renderSSR());
}
if (profile?.hints.deferHeavyComponents) {
return reply.send(renderLite());
}
reply.send(renderFull());
});
app.listen({ port: 3000 });How it works
- Probe endpoint receives device signals from the browser and stores a classified profile
- Middleware registers a
preHandlerhook that reads the session cookie, loads the profile from storage, and attaches it toreq.deviceProfile - Your route handlers use
req.deviceProfile.hintsandreq.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
});When injectProbe is enabled, the middleware registers an onSend hook that injects the script before </head>.
Streaming responses: The
onSendhook receives the serialized payload as a string. If you stream responses viareply.raw, the hook is bypassed and injection is 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 \| ((req: FastifyRequest) => 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 preHandler hookcreateProbeEndpoint(options)— Standalone probe endpoint handlercreateInjectionMiddleware(options)— Standalone onSend injection hook
Compatibility
- Fastify 5.x
@fastify/cookie11.x- Node.js >= 20
License
MIT
