Cómo crear una comunidad online en Next.js

Un foro guarda lo que escribieron sus miembros. Una comunidad que se siente viva tiene más cosas pasando a la vez. Los miembros comparten una foto y reciben una respuesta mientras siguen ahí, entran en una sala para hablar, con la voz o con la cámara, ven en directo los eventos del club y suben al escenario con su propio trabajo, y ven quién está ahora mismo. En una app de Next.js, cada una de estas cosas es una conexión que sigue abierta, con muchas personas a la vez.
Este artículo recorre cada parte que necesita una app de comunidad real, con un ejemplo completo que funciona: Community, el club de fotografía de nuestra guía de Next.js, con sus canales, sus salas para pasarse un rato y sus eventos en directo. Puedes probarlo en vivo: entra como Mia, que anima el club y cuya revisión de portfolio empieza ahora, abre el estudio y sal en directo. Luego abre el club en una ventana privada como Ava, mira la revisión, levanta la mano y sube al escenario.
Lo que necesita una comunidad online
- Canales al día. Los mensajes y las fotos llegan al instante a todos los que están leyendo, con el aviso de quién escribe y las reacciones, y se guardan para quienes llegan después. Las fotos solo se abren para los miembros.
- Salas para pasarse. Salas de voz, y salas con cámaras y pantallas, en las que los miembros entran y salen cuando quieren, muchas personas a la vez a través de servidores de medios (cómo funcionan los servidores de medios). Anfitriones que pueden silenciar a alguien o sacarlo.
- Eventos en directo. Un anfitrión para todos los miembros, con un chat y reacciones junto a la imagen, y un miembro al que se sube al escenario para mostrar su trabajo (cómo funcionan las transmisiones en directo en Next.js).
- Lo que está pasando ahora. Quién está en cada sala y qué evento está en directo, al día en cada página, sin recargar.
- Quién está. Quién está en línea, y una forma de escribirle o llamarle.
Todo esto va sobre conexiones que siguen abiertas mientras un miembro está en el sitio, algo que una función serverless no puede mantener (por qué, y qué hacer). Puedes construir cada parte tú mismo, sobre websockets, WebRTC y servidores de medios propios. O dejas que una nube en tiempo real se encargue de las conexiones, las llamadas y las transmisiones, mientras tu servidor conserva lo que es realmente tuyo: los miembros, las reglas y los datos. Eso es lo que hace el club, con WordPlus Cloud.
El ejemplo: un club de fotografía
Los miembros comparten fotos en canales para recibir comentarios, como #critique y #trips. Se pasan por sus salas: Coffee lounge con la voz, Editing hangout con cámaras y pantallas. Los anfitriones llevan eventos en directo, una revisión de portfolio o una clase de fotografía nocturna, en los que los miembros suben al escenario. Es una app de Next.js con App Router, Better Auth y Postgres, y su parte en tiempo real son unos pocos archivos. Cinco pasos la hacen.
1. Canales con fotos, guardados en tu servidor
Cada canal es una sala con el nombre de su número: channel:2. El sitio los declara una sola vez, por ese patrón, con la regla de quién puede estar en uno:
const MESSAGES = { history: 50, emit: ['message', 'typing', 'reaction', 'read'], webhook: ['message', 'reaction'] };
realtime.room('channel:{id}', MESSAGES, async (user, _params, room) => mayWrite(user, room));Cualquier miembro puede estar en un canal, y nadie más. Los mensajes, el aviso de quién escribe, las reacciones y las marcas de leído van de página en página a través de WordPlus Cloud, sellados, y los webhooks message y reaction llevan cada uno al servidor del sitio, que lo guarda en su propia base de datos. La página del canal es el chat ya hecho sobre esa sala:
<Chat
rt={useRealtime()}
room={room}
people={people}
messages={`/api/messages?room=${room}`}
upload="/api/files"
accept="image/jpeg,image/png,image/webp,image/gif"
maxFileSize={15 * 1024 * 1024}
/>messages es la ruta propia del sitio para los mensajes anteriores, que se cargan a medida que un miembro sube por la conversación. upload le da al cuadro de texto su clip, y también acepta fotos pegadas o soltadas sobre el chat: cada una va a la ruta propia del sitio en cuanto se elige, y el mensaje solo lleva dónde se abre. WordPlus Cloud nunca ve la foto. La ruta decide con la regla de la propia sala:
if (!me || !(file instanceof File) || !/^(channel|chat):\d+$/.test(room) || !(await realtime.mayJoin(room, me))) {
return Response.json({ error: 'This photo is not for this conversation.' }, { status: 403 });
}realtime.mayJoin() ejecuta la misma regla que entrar en la sala, así que quien puede leer un canal puede compartir una foto en él, y nadie más. Cada foto se abre en una dirección propia, que vuelve a comprobar quién la abre: alguien de fuera del club no recibe nada, tenga la dirección que tenga.
2. Salas de voz que cuidan los anfitriones
Una sala es una llamada en grupo con el nombre de su número, lounge:3, en la que cualquier miembro puede entrar. La regla hace de los anfitriones del club sus anfitriones:
realtime.groupCalls('lounge:{id}', async (user) => {
if (!(await isMember(user))) return false;
return (await isHost(user)) ? { admin: true } : true;
});La sala se muestra en su propia página, con el botón para entrar dentro de ella:
<CallSpot rt={rt} call={room} className="h-[calc(100dvh-14rem)] rounded-2xl">
<GroupCallButton rt={rt} room={room} type={kind} title={name}>
{people.length > 0 ? 'Drop in' : `Open ${name}`}
</GroupCallButton>
</CallSpot>type es audio para una sala de voz y video para una con cámaras. Antes de que un miembro entre, CallSpot muestra lo que tiene dentro: quién está ahora en la sala, y el botón. Una vez dentro, la llamada ocupa su lugar: la imagen de todos, o sus cámaras y pantallas, y los controles. Un miembro entra con el micrófono y la cámara apagados y los enciende desde la llamada, así que pasarse nunca abre un micrófono por sorpresa. En otra página del sitio, la sala sigue en una ventana en la esquina, y vuelve cuando su página se abre de nuevo.
admin: true es todo lo que necesitan los anfitriones. En la pantalla de un anfitrión, cada recuadro de los demás tiene silenciar y expulsar, que la pantalla de llamada pide por la misma ruta, y que esta solo acepta de un anfitrión.
3. Eventos en directo, con miembros en el escenario
Un evento es una transmisión en directo con el nombre de su número: event:5. Su regla dice quién es quién: el anfitrión del evento sale en directo, y cualquier otro miembro mira:
realtime.streams(
'event:{id}',
async (user, { id }) => {
const event = await eventOf(id);
if (!event || !(await isMember(user))) return false;
return String(user) === event.host ? { role: 'host', quality: '1080p' } : 'viewer';
},
{ stage: true, reactions: true },
);El anfitrión sale en directo desde un estudio propio, una página del sitio con la imagen grande, Go live debajo, y al lado el chat y las manos levantadas:
<LiveStream
name={`event:${event.id}`}
view="studio-page"
title={event.title}
url={`/events/${event.id}`}
backUrl={`/events/${event.id}`}
/>La página del evento da a cada miembro el reproductor con su chat, el mismo <LiveStream> con el mismo nombre. Un anfitrión también puede salir en directo desde OBS, con la dirección y la clave que da el estudio. stage: true es el escenario: un miembro levanta la mano desde el reproductor, el anfitrión la ve en Guests y lo invita, y en cuanto pulsa Join stage, su cámara y su micrófono salen en directo junto al anfitrión, para todos los que miran.
4. Lo que está pasando ahora, en cada página
Junto a cada página, una barra lateral muestra quién está en cada sala y qué evento está en directo, con cuántos lo miran. El sitio no guarda ninguna tabla de eso. WordPlus Cloud lo sabe:
export async function liveNow(): Promise<Map<string, number>> {
const live = new Map<string, number>();
for (const stream of await realtime.listStreams()) {
const event = idIn(stream.name, 'event');
if (event) live.set(event, stream.viewers);
}
return live;
}realtime.listCalls() y realtime.call(name).get() dicen lo mismo de las salas y de quién está en cada una. Para mantenerlo al día, WordPlus Cloud envía al sitio un webhook cada vez que alguien entra en una sala o sale de ella, o un evento empieza o termina. El sitio lo dice en una sala a la que se une cada página de un miembro, y cuando un evento sale en directo, se lo dice a cada miembro:
realtime.on('stream.live', async ({ stream }) => {
const id = idIn(stream, 'event');
const event = id ? await eventOf(id) : null;
if (!event) return;
await realtime.to('club').emit('changed');
const everyone = (await members()).map((person) => person.id).filter((person) => person !== event.host);
await realtime.toUser(everyone).emit('live', { event: event.id, title: event.title, host: event.hostName });
});En el layout, cada página escucha las dos cosas:
useRoom('club', { changed: () => router.refresh() });
useUserEvent('live', (data: { event: string; title: string; host: string }) => {
if (path !== `/events/${data.event}`) show({ text: `${data.host} is live: ${data.title}`, href: `/events/${data.event}`, live: true });
});La primera hace que cada página se pida de nuevo al servidor, sin recargar, así que la barra lateral y la página abierta siguen al día. La segunda muestra un aviso en la página en la que esté un miembro: «Mia is live: Live portfolio review», que abre el evento. group.joined, group.left y stream.ended dicen changed en la misma sala.
5. Quién está en línea
WordPlus Cloud sabe quién está en línea: cada página cuyo miembro ha avalado el sitio pone a ese miembro en línea, y la página se entera de quién llega y quién se va, sin ninguna petición al servidor del sitio:
const { online } = useOnline(people.map((person) => person.id));
const here = people.filter((person) => online[person.id]);
const away = people.filter((person) => !online[person.id]);La lista de miembros pone primero a quienes están en línea, cada uno con Message y Call, y junto a cada página el layout muestra el club de la misma forma, con los anfitriones marcados. La página de un miembro dice si está en línea, o cuándo se le vio por última vez.
Lo que viene incluido
- Conversaciones entre dos. La página de un miembro tiene tu conversación con esa persona, fotos incluidas, y su llamada se muestra junto a los mensajes.
- Reacciones y un chat en cada evento, junto a la imagen, y salir en directo desde OBS o cualquier codificador que envíe por WHIP.
- Compartir pantalla en una sala con cámaras, desde los controles de la llamada.
- Nombres y caras de tu app. Cada recuadro, mensaje y escenario muestra el nombre y la foto de un miembro tal como los da el
profiledel sitio, sin ninguna lista de usuarios guardada en WordPlus Cloud. - Retirar el acceso. Cuando alguien deja el club, las reglas dicen que no desde entonces, y
realtime.call(name).remove(user)lo saca de una sala al instante.
Pruébalo y luego construye el tuyo
En la demo en vivo, el club es solo tuyo durante un día, así que nadie más ve lo que compartas ni con quién te encuentres, y un evento o una sala termina solo a los 10 minutos. Debajo de cada página, How this page works muestra el código que la hace funcionar, paso a paso, con los valores de la página que tienes abierta.
La guía del sitio de comunidad construye el sitio entero, con cada archivo de su parte en tiempo real completo. Las transmisiones en directo en Next.js y las llamadas en Next.js reúnen todas las formas de poner una transmisión o una llamada en tu propia app. Para un equipo en lugar de un club, la videoconferencia en Next.js construye las reuniones de una intranet de equipo.
WordPlus Cloud for Apps tiene un precio según las conexiones simultáneas, a partir de 1.000, con facturación mensual o anual. Con 1.000 conexiones, las transmisiones en directo no se miden hasta 50 espectadores a la vez, y las llamadas en grupo hasta 32 personas con vídeo a la vez, contando como media a una persona solo con audio. Cada 1.000 conexiones más añaden 20 espectadores y 12 personas con vídeo. Por encima, cada espectador o persona con vídeo adicional gasta un crédito por minuto, una transmisión contada solo mientras está en directo, y las llamadas entre dos personas no gastan ninguno. Descubre las transmisiones en directo para apps, las videollamadas para apps, o consulta los precios para apps. ¿Preguntas sobre tu integración? Escríbenos.