Реализация real-time чата на Next.js с использованием WebSockets

#nextjs#websockets#realtime#apinator

TL;DR

Разбираем реализацию real-time чата на Next.js с использованием WebSockets через Apinator — hosted-решение для real-time коммуникации. Покроем настройку private/presence-каналов, server-side авторизацию и оптимистичные UI-обновления. Готовый пример поддерживает список онлайн-пользователей и мгновенную доставку сообщений.

Введение: зачем ещё один чат?

В 2024 году real-time функциональность стала must-have для многих приложений: от чатов до collaborative editing. Но нативные WebSockets требуют:

Apinator решает эти проблемы как hosted-альтернатива с Pusher-совместимым API. В этом гайде мы используем его с Next.js App Router для создания полноценного чата с:

Инициализация проекта

Создаём Next.js 14 проект с TypeScript и Tailwind:

npx create-next-app@latest realtime-chat --typescript --tailwind --app

Устанавливаем Apinator SDK:

npm install @apinator/client @apinator/server

Конфигурация в .env.local:

NEXT_PUBLIC_APINATOR_KEY=your_key
NEXT_PUBLIC_APINATOR_CLUSTER=eu
APINATOR_APP_ID=your_app_id
APINATOR_SECRET=your_secret

Server-side: авторизация каналов

Private/presence-каналы требуют server-side авторизации. Создаём API route:

// app/api/auth/channel/route.ts
import { Apinator } from "@apinator/server";

const apinator = new Apinator({
  appId: process.env.APINATOR_APP_ID!,
  secret: process.env.APINATOR_SECRET!,
  cluster: process.env.NEXT_PUBLIC_APINATOR_CLUSTER! as "eu" | "us",
});

export async function POST(req: NextRequest) {
  const { socket_id, channel_name } = await req.json();
  
  const channelData = channel_name.startsWith("presence-")
    ? JSON.stringify({
        user_id: socket_id,
        user_info: { name: req.headers.get("x-username") ?? "Anonymous" }
      })
    : undefined;

  const auth = apinator.authenticateChannel(socket_id, channel_name, channelData);
  return NextResponse.json(auth);
}

Обработка сообщений

API route для broadcast сообщений:

// app/api/messages/route.ts
await apinator.trigger({
  name: "new-message",
  channel: "presence-chat-room",
  data: JSON.stringify({
    text: text.trim(),
    username,
    timestamp: Date.now()
  }),
  socketId // исключаем отправителя
});

Client-side: хук useChat

Реализуем кастомный хук для управления WebSocket-соединением:

// hooks/useChat.ts
const client = new Apinator({
  key: process.env.NEXT_PUBLIC_APINATOR_KEY!,
  cluster: process.env.NEXT_PUBLIC_APINATOR_CLUSTER! as "eu" | "us",
  authEndpoint: "/api/auth/channel",
  authHeaders: { "x-username": username }
});

const channel = client.subscribe("presence-chat-room") as PresenceChannel;

channel.bind("realtime:subscription_succeeded", () => {
  setMembers(channel.getMembers() as OnlineMember[]);
});

UI-компонент чата

Интегрируем хук в страницу:

// app/chat/page.tsx
const { messages, members, sendMessage } = useChat(username);

<form onSubmit={(e) => {
  e.preventDefault();
  sendMessage(input);
  setInput("");
}}>
  <input 
    value={input}
    onChange={(e) => setInput(e.target.value)}
  />
</form>

Оптимизации и прод

  1. Оптимистичные обновления:
setMessages(prev => [...prev, {
  id: `${Date.now()}-self`,
  text,
  self: true
}]);
  1. Троттлинг событий:
const typingTimeout = useRef<NodeJS.Timeout>();

channel.bind("client-typing", ({ username }) => {
  setTypingUser(username);
  clearTimeout(typingTimeout.current);
  typingTimeout.current = setTimeout(() => setTypingUser(null), 1000);
});
  1. Error boundaries для обработки разрывов соединения:
<ErrorBoundary fallback={<ReconnectButton />}>
  <Chat />
</ErrorBoundary>

Альтернативы и когда что выбрать

РешениеПлюсыМинусыUse case
Native WSПолный контрольСложность масштабированияВнутренние сервисы
ApinatorБыстрый стартVendor lock-inСтартапы, MVP
AblyГибкостьЦенаКритичные системы
Socket.ioFallback pollingУстаревший подходЛегаси проекты

Заключение

Полученное решение даёт:

Для прод-использования добавьте:

Apinator особенно хорош для стартапов — вы получаете production-ready real-time функциональность без необходимости настраивать собственные WebSocket-серверы.


Источник: https://dev.to/itsshpetim/build-a-real-time-chat-app-with-nextjs-5794