flowpanel pushes live updates to open browsers over Server-Sent Events. On a
single instance the default `memory` driver works out of the box. The moment
you run **more than one Next.js instance**, you need the `redis` driver so an
event raised on one instance reaches a browser connected to another.

> For the full production checklist (proxy buffering, TLS, LB idle timeouts,
> rolling-deploy hygiene), see
> [Realtime in production](/docs/core-concepts/realtime-multi-instance).

## 1. Enable realtime on a resource

Opt a resource into live updates with `realtime: true`. Lists for that
resource refresh automatically when a row is created, updated, or deleted:

```ts
import { resource } from "@flowpanel/kit";
import * as schema from "@/db/schema";

resource(schema.orders, {
  columns: ["id", "status", "total"],
  realtime: true,
});
```

## 2. Point the publisher at Redis

`realtime` takes the publisher options directly. Switch the driver to
`redis` and pass your connection URL:

```ts
import { defineAdmin } from "@flowpanel/kit";

export default defineAdmin({
  // …adapter, auth, resources…
  realtime: {
    driver: "redis",
    url: process.env.REDIS_URL!,
    // Optional: namespaces channels so multiple admins can share one Redis.
    // Defaults to "flowpanel".
    keyPrefix: "fp",
  },
});
```

That's the entire switch. Every `resource.realtime: true` and every
`publishResource(...)` call now fans out over Redis pub/sub instead of an
in-process emitter. The `app/api/flowpanel/stream/route.ts` SSE endpoint —
generated by `flowpanel init` — is unchanged.

flowpanel publishes only the **resource name + action** over Redis, never row
contents. Browsers receive the ping and re-fetch the updated rows over HTTPS,
so Redis is the notification channel, not the source of truth.

## 3. Publish custom events (optional)

For dashboards or background jobs, push your own resource event from a
Server Action or route handler:

```ts
import { publishResource } from "@flowpanel/kit/next";

await db.update(schema.orders).set({ status: "shipped" }).where(/* … */);
await publishResource("orders", { action: "update", id: orderId });
```

Any list or widget subscribed to `orders` refreshes the moment the event
lands.

## When the `memory` driver is enough

- `next dev`
- A single-container / single-instance deployment
- Previews where cross-instance fan-out doesn't matter

In those cases leave `realtime` as `{ driver: "memory" }` (or omit it — memory
is the default).
