Next.js Integration (Temporary Workaround)
Pulse does not yet ship a dedicated Next.js wrapper (like @pulse/express
or @pulse/nest). The approach below wires the core @pulse/server SDK into
the Next.js App Router by hand. It works today, but treat it as an interim
solution until an official @pulse/next package lands.
A complete, runnable reference implementation lives in the pulse-web repo:
dev/pulse-next-js-dev.
Why a workaround is needed
Next.js has no real server middleware chain. Its middleware.ts runs on the
Edge runtime as a lightweight proxy — not a Node.js request handler — so we
cannot inject Pulse the way we do for Express or NestJS.
Two consequences follow:
- We can't use middleware to open the request context. Instead, each App Router route handler is wrapped explicitly so it runs inside a Pulse request-scoped context.
- Route handlers using Pulse must run on the Node.js runtime. The Pulse
Server SDK relies on Node's
async_hooks/AsyncLocalStorage, which the Edge runtime does not provide.
Step 1 — Create a shared Pulse helper
Create a single PulseClient at module-load time and expose a withPulse
wrapper that opens a request-scoped context via
client.createAsyncRequestContext. That context is what pulse.* (from
@pulse/core) resolves against for the lifetime of the request.
The client is cached on globalThis so Next.js hot reloads (which re-evaluate
modules on every edit in dev) don't rebuild transports and caches on each
reload.
// src/lib/pulse.ts
import { createClient, PartialServerConfigurations, PulseClient } from '@pulse/server';
import type { NextRequest } from 'next/server';
const PULSE_APP = 'picsart.com';
const PULSE_CONFIG: PartialServerConfigurations = {
app: PULSE_APP,
settings: {
host: 'https://optifyr.com',
timeout: 30_000,
offline: false,
cache: true,
contextCacheTTL: 600_000,
},
};
// Reuse a single client across requests and across dev hot reloads.
const globalForPulse = globalThis as typeof globalThis & {
θpulseClient?: PulseClient;
};
export const pulseClient: PulseClient = (globalForPulse.θpulseClient ??=
createClient(PULSE_CONFIG));
type RouteHandler<Args extends unknown[]> = (
request: NextRequest,
...args: Args
) => Response | Promise<Response>;
/**
* Wraps an App Router route handler so it runs inside a Pulse request context.
* The state cookie is written back onto the returned `Response` automatically
* by `createAsyncRequestContext`.
*/
export function withPulse<Args extends unknown[]>(
handler: RouteHandler<Args>,
): (request: NextRequest, ...args: Args) => Promise<Response> {
return (request, ...args) =>
pulseClient.createAsyncRequestContext(
// The client config's `app` is not auto-injected into the request
// context, so we seed it here (same as the Express/Nest wrappers).
PULSE_CONFIG,
() => handler(request, ...args),
request,
);
}
Step 2 — Wrap your route handlers
Wrap each App Router route handler with withPulse, and pin the route to the
Node.js runtime. Inside the handler, resolve settings with pulse.get(...) from
@pulse/core, exactly as in the other server integrations.
// src/app/api/checkout-modal/route.ts
import { pulse } from '@pulse/core';
import { NextResponse } from 'next/server';
import { withPulse } from '../../../lib/pulse';
// Pulse's server SDK relies on Node's async_hooks, so this route must run on
// the Node.js runtime (not Edge).
export const runtime = 'nodejs';
export const GET = withPulse(async () => {
const create_widgets_dashboard = await pulse.get(
'create_widgets_dashboard',
'create_widgets_dashboard',
{ defaultValue: true },
);
return NextResponse.json({ create_widgets_dashboard });
});
Key points
- No middleware. Wrap every route handler that needs Pulse with
withPulse; there is no global registration step. export const runtime = 'nodejs'is required on any route that touches Pulse — the Edge runtime lacksasync_hooks.- Seed
appin the context. The client config'sappis not auto-injected into the request context, sowithPulsepassesPULSE_CONFIGagain — mirroring what the Express and NestJS wrappers do internally. - The state cookie is written back automatically onto the
Responsereturned fromcreateAsyncRequestContext. - Cache the client on
globalThisto survive dev hot reloads.
Reference implementation
The full example — Nx project setup, next.config.mjs, ambient CSS type
declarations, and the route handlers above — is maintained in pulse-web:
dev/pulse-next-js-dev— Pulse Server SDK + SSR integration for Next.js.
Until an official @pulse/next wrapper is available, use this directory as the
canonical starting point.