---
sidebar_position: 3
sidebar_label: Presence and online
---

# Presence in Next.js

Three ways to show people: a presence room lists who is in it to everyone in it (who is in the shop, who is viewing a document). A count says how many, at any size. And who is online is known across your whole app, for a dot on every avatar.

![The browsers of Ann, Ben and Cara, each listing the three of them in the shop now, with their roles from the info of each member](/img/apps/presence-shop.webp)

## A presence room

```ts
// lib/realtime.ts: who is in the shop, everyone signed in, with their name and role.
realtime.room( 'shop', { presence: true }, async ( user ) => {
    const person = user === null ? null : await db.user.findUnique( { where: { id: user } } );
    return person ? { info: { name: person.name, role: person.role } } : false;
} );
```

- **`info`** is what the others see of the member: a name, a role, anything small (up to 4 KB). It is sealed with the room key.
- **Up to 1,000 members** are listed in full, with `join` and `leave`. Past that, the room counts them, and `members()` pages through them, up to 100,000.

```tsx
'use client';

import { usePresence } from '@wordplus/realtime/react';

export function InTheShop() {
    const members = usePresence( 'shop' );   // [ { id, info }, … ], kept current
    return <ul>{ members.map( ( m ) => <li key={ m.id }>{ ( m.info as { name: string } ).name }</li> ) }</ul>;
}
```

A user is a member once, however many tabs and devices they have in the room, and leaves when the last one does.

## Counting

`count: true` gives a room a live count, without a list:

```ts
realtime.room( 'product:{id}', { public: true, count: true } );
```

```tsx
const { count } = useRoom( `product:${ id }` );   // at most once a second
return count ? <p>{ count } looking at this</p> : null;
```

A presence room counts too.

## Who is online, across your app

Every page whose user your server vouched for makes that user online. They go offline 10 seconds after their last page closes, so a reload does not flicker.

```tsx
const { online, lastSeen, count } = useOnline( [ 34, 43 ], { count: true } );
// online[ '34' ]: true or false, lastSeen[ '43' ]: when, in ms, for one offline, count: how many are online
```

- **A page watches** up to 1,000 users, and gets their status now and every change.
- **Your server asks too:** `await realtime.online.has( 42 )`, `.count()`, `.list()`.
- **Hiding:** a user who chose not to show their status is never online to anyone:

```ts
export const realtime = createRealtime( {
    // …
    hidden: async ( user ) => ( await db.user.findUnique( { where: { id: user } } ) )?.hideStatus === true,
} );
```

## Ready-made: who is online

The Better Messages list of members, on a presence room, with a call button for each of the others:

```tsx
import { OnlineCount, OnlineList } from '@wordplus/realtime/react';

<OnlineList room="shop" title="In the shop now" call="support:{pair}" type="video" />
<OnlineCount room={ `product:${ id }` } />
```

- **It loads** with `<CallScreen />` in your layout, or `<RealtimeUi />` on pages without calls.
- **`call`** names the call the button of each row places: `{pair}` is the two user ids in order (`support:34:42`), `{me}` and `{id}` each one. Without it, the rows have no button.
- **`type`** is `video`, `audio`, or both: `type="audio video"`.
- **Names and pictures** come from the member info, then from your [directory](encryption.md#your-users).
- **Its look** is that of Better Messages, in a shadow root: your styles do not reach in, and its colours are CSS custom properties your page sets.

More, with PHP and Python side by side: [presence in depth](../apps/presence.md).
