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.

A presence room
// 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;
} );
infois 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
joinandleave. Past that, the room counts them, andmembers()pages through them, up to 100,000.
'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:
realtime.room( 'product:{id}', { public: true, count: true } );
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.
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:
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:
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. callnames 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.typeisvideo,audio, or both:type="audio video".- Names and pictures come from the member info, then from your directory.
- 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.