Use this recipe after deciding that the single-process memory driver is insufficient. It keeps the browser SSE route unchanged and replaces the process-local publisher with Redis pub/sub.

## Install Redis support

```bash
pnpm add ioredis
```

Provide `REDIS_URL` through the deployment environment. Use TLS and credentials appropriate for the Redis provider.

## Configure the publisher

```ts excerpt
export default defineAdmin({
  adapter,
  auth,
  realtime: {
    driver: "redis",
    url: process.env.REDIS_URL,
    keyPrefix: "myapp-production",
  },
  resources,
});
```

Use a distinct key prefix for every application and environment sharing the same Redis service. Resources or widgets subscribe to their normal application channel names; the prefix is applied on the transport.

## Publish from an external worker

A worker, a cron job or a one-off script publishes through `@flowpanel/kit/publish`. It is the whole realtime client: no admin config, no adapter, no Next.js — one connection and the wire format.

```ts excerpt
import { createPublisher } from "@flowpanel/kit/publish";

const events = createPublisher({ redis: process.env.REDIS_URL! });

await events.publish(events.channelFor("orders"), { action: "update", id: orderId });
await events.close();
```

`channelFor("orders")` returns `resource.orders`, the channel a resource subscribes to when it sets `realtime: true`; any other string is a channel of your own. Use the same `keyPrefix` the admin uses, or neither side sees the other:

```ts excerpt
const events = createPublisher({ redis: process.env.REDIS_URL!, keyPrefix: "myapp-production" });
```

Create the publisher once per process and `close()` it on shutdown. A publish-only process opens exactly one connection, on the first publish — nothing connects while the module is merely imported.

When the worker already owns a configured Redis client — TLS, a sentinel, a retry strategy, a BullMQ connection — hand that over instead of a URL, and it stays yours to close:

```ts excerpt
import { createPublisher } from "@flowpanel/kit/publish";
import { redis } from "./redis";

const events = createPublisher({ redis });
```

Inside the admin itself nothing changes: `ctx.publish` in an action, or `publishResource` from `@flowpanel/kit/server`, already use the publisher `defineAdmin` configured.

## Verify across instances

Run two application instances against the same Redis key prefix. Connect a browser to one, perform a mutation through the other, and confirm the browser refetches. Repeat with a worker publication. If local same-instance updates work but this test fails, inspect the prefix, the Redis URL, and network/TLS settings. FlowPanel also warns on startup when it resolves the memory driver while `REDIS_URL` is set in the environment, which is the configuration that produces exactly this symptom.

## Production notes

Redis publication distributes refresh signals; it does not make SSE durable. Monitor publish errors and reconnects, and decide whether a failed post-commit notification should alert, retry, or wait for the next user refresh.

Return to the full [Realtime production checklist](/docs/guides/realtime).
