You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 14 App Router中WebSocket连接无法持久化的求助

解决Next.js 14 App Router中WebSocket路由切换时隐式关闭的问题

问题根源

你遇到的核心问题是Next.js开发模式(next dev)的Fast Refresh热更新机制会导致模块被重复加载,即使是单例模块也会被重新实例化,进而创建新的WebSocket连接并关闭旧连接。另外,App Router的客户端组件在路由切换时,虽然根布局未卸载,但模块热更新触发的代码重新执行会破坏原有的单例逻辑。

有效解决方案

1. 基于globalThis实现全局唯一WebSocket实例

利用浏览器的全局对象globalThis存储WebSocket实例,避免模块重复加载时实例丢失:

// WebSocketService.ts
class WebSocketService {
  private static WS_URL = 'ws://your-csharp-server-address';

  static getInstance(): WebSocket {
    // 使用globalThis存储全局唯一实例
    if (!globalThis.__APP_WS_INSTANCE__) {
      console.log('初始化新WebSocket连接');
      globalThis.__APP_WS_INSTANCE__ = new WebSocket(this.WS_URL);

      // 绑定基础事件
      globalThis.__APP_WS_INSTANCE__.onmessage = (event) => {
        // 全局消息处理,可通过自定义事件分发到各组件
        window.dispatchEvent(new CustomEvent('ws-message', { detail: event.data }));
      };

      globalThis.__APP_WS_INSTANCE__.onclose = () => {
        console.log('连接关闭,3秒后尝试重连');
        setTimeout(() => WebSocketService.getInstance(), 3000);
      };

      globalThis.__APP_WS_INSTANCE__.onerror = (error) => {
        console.error('WebSocket错误:', error);
      };
    }
    return globalThis.__APP_WS_INSTANCE__;
  }

  static sendMessage(data: string | ArrayBufferLike | Blob | ArrayBufferView) {
    const instance = globalThis.__APP_WS_INSTANCE__;
    if (instance && instance.readyState === WebSocket.OPEN) {
      instance.send(data);
    }
  }

  static closeConnection() {
    const instance = globalThis.__APP_WS_INSTANCE__;
    if (instance) {
      instance.close();
      delete globalThis.__APP_WS_INSTANCE__;
    }
  }
}

// 开发模式下拦截热更新,避免重置实例
if (process.env.NODE_ENV === 'development' && module.hot) {
  module.hot.accept(() => {
    console.log('热更新保留现有WebSocket连接');
  });
}

export default WebSocketService;

2. 在根Layout中初始化全局连接

在根layout.tsx中通过useEffect仅初始化一次连接,同时处理应用卸载时的清理逻辑:

// app/layout.tsx
'use client';

import { useEffect } from 'react';
import WebSocketService from './WebSocketService';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    // 初始化全局WebSocket连接
    WebSocketService.getInstance();

    // 应用卸载时关闭连接(仅页面刷新/关闭时触发)
    const handleUnload = () => {
      WebSocketService.closeConnection();
    };
    window.addEventListener('beforeunload', handleUnload);

    return () => {
      window.removeEventListener('beforeunload', handleUnload);
    };
  }, []);

  return (
    <html lang="zh-CN">
      <body>{children}</body>
    </html>
  );
}

3. 组件中接收WebSocket消息

各客户端组件通过监听自定义事件接收消息,无需直接持有WebSocket实例:

// app/page.tsx
'use client';

import { useEffect, useState } from 'react';

export default function Home() {
  const [messages, setMessages] = useState<string[]>([]);

  useEffect(() => {
    const handleWsMessage = (event: CustomEvent) => {
      setMessages(prev => [...prev, event.detail]);
    };
    window.addEventListener('ws-message', handleWsMessage as EventListener);

    return () => {
      window.removeEventListener('ws-message', handleWsMessage as EventListener);
    };
  }, []);

  const sendTestMessage = () => {
    import('./WebSocketService').then(module => {
      module.default.sendMessage('Hello from Next.js client');
    });
  };

  return (
    <div>
      <button onClick={sendTestMessage}>发送测试消息</button>
      <ul>
        {messages.map((msg, idx) => <li key={idx}>{msg}</li>)}
      </ul>
    </div>
  );
}

关键说明

  • globalThis在浏览器环境中是全局对象,不受模块热更新影响,确保实例唯一
  • 开发模式下通过module.hot.accept拦截热更新,避免重新创建连接
  • 自定义事件ws-message实现消息的全局分发,组件无需直接依赖WebSocket实例
  • 自动重连逻辑作为兜底,应对意外断开的情况

内容的提问来源于stack exchange,提问作者user11672881

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 17:23:16