Меньшинству, но все же огромному числу проектов требуется интеграция веб-сокетов, чтобы обеспечить мгновенную реакцию интерфейса на изменения без повторной загрузки данных. Это важная вещь, и мы не собираемся говорить о них или сравнивать сторонние библиотеки, предоставляющие API для лучшего опыта разработки. Моя цель — показать, как быстро интегрировать с NextJs. И как решить проблемы, с которыми мы столкнулись во время разработки. @microsoft/signalr Я надеюсь, что все уже установили и развернули проект NextJS локально. В моем случае версия . Добавим еще несколько важных библиотек: (версия ) для выборки данных и дальнейшей работы с локальным кешем и (версия ) — API для веб-сокетов. 13.2.4 swr 2.1.5 @microsoft/signalr 7.0.5 npm install --save @microsoft/signalr swr Давайте начнем с создания простой функции и нового хука под названием для получения исходных данных из нашего REST API. Он возвращает список сообщений для чата, поля, определяющие ошибки и состояние загрузки, а также метод , позволяющий изменять кэшированные данные. fetcher useChatData mutate // hooks/useChatData.ts import useSWR from 'swr'; type Message = { content: string; createdAt: Date; id: string; }; async function fetcher<TResponse>(url: string, config: RequestInit): Promise<TResponse> { const response = await fetch(url, config); if (!response.ok) { throw response; } return await response.json(); } export const useChatData = () => { const { data, error, isLoading, mutate } = useSWR<Message[]>('OUR_API_URL', fetcher); return { data: data || [], isLoading, isError: error, mutate, }; }; Чтобы проверить, работает ли он должным образом, давайте обновим компонент нашей страницы. Импортируйте наш крючок вверху и извлеките из него данные, как показано в фрагменте ниже. Если это сработает, вы увидите визуализированные данные. Как видите, это довольно просто. // pages/chat.ts import { useChatData } from 'hooks/useChatData'; const Chat: NextPage = () => { const { data } = useChatData(); return ( <div> {data.map(item => ( <div key={item.id}>{item.content}</div> ))} </div> ); }; Следующий шаг требует подключения нашей будущей страницы к веб-сокетам, перехвата событий и обновления кеша новым сообщением. Предлагаю начать с сборки сервиса сокетов в отдельном файле. NewMessage Согласно примерам в документации SignalR, нам необходимо создать экземпляр соединения для дальнейшего прослушивания событий. Я также добавил объект соединений для предотвращения дублирования и двух помощников для запуска/остановки соединений. // api/socket.ts import { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr'; let connections = {} as { [key: string]: { type: string; connection: HubConnection; started: boolean } }; function createConnection(messageType: string) { const connectionObj = connections[messageType]; if (!connectionObj) { console.log('SOCKET: Registering on server events ', messageType); const connection = new HubConnectionBuilder() .withUrl('API_URL', { logger: LogLevel.Information, withCredentials: false, }) .withAutomaticReconnect() .build(); connections[messageType] = { type: messageType, connection: connection, started: false, }; return connection; } else { return connections[messageType].connection; } } function startConnection(messageType: string) { const connectionObj = connections[messageType]; if (!connectionObj.started) { connectionObj.connection.start().catch(err => console.error('SOCKET: ', err.toString())); connectionObj.started = true; } } function stopConnection(messageType: string) { const connectionObj = connections[messageType]; if (connectionObj) { console.log('SOCKET: Stoping connection ', messageType); connectionObj.connection.stop(); connectionObj.started = false; } } function registerOnServerEvents( messageType: string, callback: (payload: Message) => void, ) { try { const connection = createConnection(messageType); connection.on('NewIncomingMessage', (payload: Message) => { callback(payload); }); connection.onclose(() => stopConnection(messageType)); startConnection(messageType); } catch (error) { console.error('SOCKET: ', error); } } export const socketService = { registerOnServerEvents, stopConnection, }; Итак, теперь наша страница может выглядеть так, как показано в фрагменте кода. Мы извлекаем и извлекаем со списком сообщений и отображаем их. Кроме того, описанный выше, регистрирует событие , создает соединение и прослушивает серверную часть. data useEffect NewMessage Когда событие срабатывает, метод из перехватчика обновляет существующий список новым объектом. mutate // pages/chat.ts import { useChatData } from 'hooks/useChatData'; import { socketService } from 'api/socket'; const Chat: NextPage = () => { const { data } = useChatData(); useEffect(() => { socketService.registerOnServerEvents( 'NewMessage', (payload: Message) => { mutate(() => [...data, payload], { revalidate: false }); } ); }, [data]); useEffect(() => { return () => { socketService.stopConnection('NewMessage'); }; }, []); return ( <div> {data.map(item => ( <div key={item.id}>{item.content}</div> ))} </div> ); }; Мне нравится, работает, и мы видим, как в ленте появляются новые сообщения. Я выбрал базовый пример с чатом, потому что он понятен и понятен. И, конечно, вы применяете это по своей собственной логике. Небольшой бонус Используя одну из версий ( ), мы столкнулись с проблемой дублирования. Он был подключен к , массиву зависимостей. Каждый раз, когда зависимость менялась, кэшировал обратный вызов и запускал его снова и снова. @microsoft/signalr useEffect connection.on(event, callback); useEffect(() => { // data equals [] by default (registerOnServerEvents 1 run), // but after initial data fetching it changes (registerOnServerEvents 2 run) // each event changes data and triggers runnning of registerOnServerEvents socketService.registerOnServerEvents( 'NewMessage', // callback cached (payload: Message) => { // mutate called multiple times on each data change mutate(() => [...data, payload], { revalidate: false }); } ); }, [data]); // after getting 3 messages events, we had got 4 messages rendered lol Самым быстрым и надежным решением, которое мы нашли, было сохранение копии данных внутри React и использование ее внутри для будущих обновлений. ref useEffect // pages/chat.ts import { useChatData } from 'hooks/useChatData'; import { socketService } from 'api/socket'; const Chat: NextPage = () => { const { data } = useChatData(); const messagesRef = useRef<Message[]>([]); useEffect(() => { messagesRef.current = chatData; }, [chatData]); useEffect(() => { socketService.registerOnServerEvents( 'NewMessage', (payload: Message) => { const messagesCopy = messagesRef.current.slice(); mutate(() => [...messagesCopy, payload], { revalidate: false }); } ); }, [data]); useEffect(() => { return () => { socketService.stopConnection('NewMessage'); }; }, []); return ( <div> {data.map(item => ( <div key={item.id}>{item.content}</div> ))} </div> ); }; В настоящее время мы используем новую версию , в которой, похоже, уже есть необходимые исправления. Но в любом случае, если кто-то найдет это решение полезным и воспользуется этим обходным путем, я буду рад. В заключение хочу сказать, что мой опыт работы с SignalR вполне положительный, для установки не потребовалось каких-то особых зависимостей или настроек, он отлично работает и покрывает наши потребности. @microsoft/signalr