next-ws

repository·main·Indexed 43 days ago

https://github.com/k0d13/next-ws

WebSocket support for the Next.js app directory, allowing developers to handle WebSocket connections directly within API routes via an UPGRADE function without a separate server. Version 2.2.15. Includes a CLI for patching Next.js installations and an Adapter interface for multi-instance deployments using pub/sub mechanisms.

Tokens
6.1K
Snippets
24
Records
27
Agent score
55%

What's inside next-ws

  1. Patch Next.js with next-ws

    main

    To enable WebSocket support, you must patch your local Next.js installation. Add a prepare script to your package.json. This ensures that next-ws patch runs automatically during installation, keeping your Next.js installation patched.

    Important Note: next-ws is designed for server-based environments. It is not suitable for serverless platforms like Vercel, as they do not support WebSocket servers. This plugin is built specifically for the Next.js app directory and does not support the pages directory.

    {
      "scripts": {
        "prepare": "next-ws patch"
      }
    }
  2. Install next-ws and ws

    main

    To use WebSockets in your Next.js app directory, install next-ws along with its peer dependency ws using your preferred package manager.

    npm install next-ws ws
    # or
    pnpm add next-ws ws
    # or
    yarn add next-ws ws
  3. Implement WebSocket handlers in Next.js routes

    main

    To handle WebSocket connections, export a handler from your Next.js page module. The library looks for specific exported names in the userland of the imported module.

    This is the preferred method. It provides access to the full Next.js request object and runs within an AsyncLocalStorage context.

    Signature: async (client: WebSocket, wsServer: WebSocketServer, request: NextRequest, context: { params: any }) => void

    Deprecated: SOCKET handler

    Signature: async (client: WebSocket, message: any, wsServer: WebSocketServer, context: { params: any }) => (closeHandler?: () => void) | Promise<(closeHandler?: () => void) | void>

  4. Create a WebSocket Echo Server

    main

    To create an echo server, export the UPGRADE function from an API route (e.g., app/api/ws/route.ts). Inside the function, listen for the message event on the client and send the received message back to the client.

    // app/api/ws/route.ts (can be any route file in the app directory)
    
    export function UPGRADE(
      client: import('ws').WebSocket,
      server: import('ws').WebSocketServer
    ) {
      console.log('A client connected');
    
      client.on('message', (message) => {
        console.log('Received message:', message);
        client.send(message);
      });
    
      client.once('close', () => {
        console.log('A client disconnected');
      });
    }
  5. Handle WebSocket connections with the UPGRADE function

    main

    In the Next.js app directory, you can handle WebSocket connections by exporting an UPGRADE function from an API route file. This function is called when a client attempts to upgrade an HTTP connection to a WebSocket connection.

    The UPGRADE function receives the following arguments:

    • client: The import('ws').WebSocket instance representing the connected client.
    • server: The import('ws').WebSocketServer instance.
    • request: The import('next/server').NextRequest object.
    • context: The import('next-ws/server').RouteContext containing route-specific information.
    export function UPGRADE(
      client: import('ws').WebSocket,
      server: import('ws').WebSocketServer,
      request: import('next/server').NextRequest,
      context: import('next-ws/server').RouteContext<'/api/ws'>,
    ) {
      // ...
    }
  6. Configure Playwright webServer dependencies

    main

    The configuration uses the webServer array to manage local development servers required for testing. Each server entry specifies a name, cwd (current working directory), command to start the server, and the port it listens on.

    To prevent conflicts in local development, reuseExistingServer is set to true (via !process.env.CI), allowing Playwright to use a server that is already running. In CI, it will always attempt to start a fresh instance.

    Managed servers include:

    • custom-server: Located in examples/custom-server, runs npm run dev on port 3003.
    • chat-room: Located in examples/chat-room, runs npm run dev -- --port 3001 on port 3001.
    • base-path: Located in examples/base-path, runs npm run dev -- --port 3002 on port 3002.

    Note: The redis-adapter tests are not managed here as they spawn their own instances.

    webServer: [
      {
        name: 'custom-server',
        cwd: 'examples/custom-server',
        command: 'npm run dev',
        env: { PORT: '3003' },
        port: 3003,
        reuseExistingServer: !process.env.CI,
      },
      {
        name: 'chat-room',
        cwd: 'examples/chat-room',
        command: 'npm run dev -- --port 3001',
        port: 3001,
        reuseExistingServer: !process.env.CI,
      },
      {
        name: 'base-path',
        cwd: 'examples/base-path',
        command: 'npm run dev -- --port 3002',
        port: 3002,
        reuseExistingServer: !process.env.CI,
      },
    ],
  7. Configure Playwright test execution settings

    main

    The Playwright configuration for this repository defines how tests are executed, reported, and how web servers are managed. Key settings include:

    • workers: Controls parallel execution. In CI environments, it is restricted to 1 worker to ensure stability.
    • testDir: The directory where tests are located (set to 'tests').
    • reporter: Configured to use the html reporter, with output directed to tests/.report.
    • retries: Set to 1 retry for failed tests.
    • use.trace: Configured to 'on-first-retry', meaning traces are captured only when a test fails and is retried.
    • outputDir: The directory for test results (set to 'tests/.results').
    export default defineConfig({
      workers: process.env.CI ? 1 : undefined,
      testDir: 'tests',
      reporter: [['html', { outputFolder: 'tests/.report' }]],
      retries: 1,
      use: { trace: 'on-first-retry' },
      outputDir: 'tests/.results',
    });
  8. Configure `SetupOptions` for multi-instance support

    main

    The SetupOptions object allows you to provide an adapter to enable cross-server communication (e.g., via Redis). When an adapter is configured:

    • The server subscribes to adapter messages for the specific pathname to broadcast to local clients.
    • Incoming client messages are automatically broadcasted via adapter.broadcast(pathname, data).
    • The adapter is automatically cleaned up when the HTTP server closes.
    export interface SetupOptions {
      adapter?: Adapter;
    }
  9. Initialize the WebSocket server with `setupWebSocketServer()`

    main

    Use setupWebSocketServer to attach a WebSocket server to an existing Next.js Node.js server instance. This function intercepts the HTTP upgrade event to route WebSocket connections to specific Next.js pages based on their pathname.

    Key Behaviors

    • Route Matching: It automatically finds the matching Next.js route for the incoming request pathname.
    • Handler Execution: It looks for exported handlers in the matched route module. It supports two types of handlers:
      • UPGRADE (Recommended): An async function that receives (client, wsServer, request, context). It runs within a request storage context.
      • SOCKET (Deprecated): An async function that receives (client, message, wsServer, context).
    • Context: The context object provided to handlers contains params extracted from the route.
    • Multi-instance Support: If an adapter is provided in SetupOptions, the server enables broadcasting across multiple instances and tracks clients by pathname.
    import { setupWebSocketServer } from 'next-ws';
    import type NextNodeServer from 'next/dist/server/next-server.js';
    
    // Assuming nextServer is your Next.js Node server instance
    setupWebSocketServer(nextServer, {
      adapter: myCustomAdapter // optional
    });
  10. Manage server instances with persistent state

    main

    The next-ws package provides utility functions to retrieve or modify the global server instances (HTTP and WebSocket) and the active adapter. These are useful for accessing the underlying server objects after initialization or for manual reconfiguration.

    import {
      getHttpServer,
      getWebSocketServer,
      getAdapter,
      setHttpServer,
      setWebSocketServer,
      setAdapter
    } from 'next-ws';
    
    const httpServer = getHttpServer();
    const wss = getWebSocketServer();
    const adapter = getAdapter();
  11. Use the deprecated useWebSocket hook

    main

    The useWebSocket hook allows you to access the current WebSocket instance from any component wrapped within a WebSocketProvider.

    Note: This hook is deprecated. Use your own implementation instead.

    Error Handling: If useWebSocket is called outside of a WebSocketProvider, it will throw the following error: useWebSocket must be used within a WebSocketProvider

    import { useWebSocket } from './path-to-context';
    
    function MyComponent() {
      const socket = useWebSocket();
    
      useEffect(() => {
        if (socket) {
          socket.onmessage = (event) => console.log(event.data);
        }
      }, [socket]);
    
      return <div>WebSocket Status: {socket ? 'Connected' : 'Disconnected'}</div>;
    }
  12. Use the Next-WS Client Context

    main

    The next-ws client-side entrypoint exports the React context provider and hooks used to manage WebSocket connections within a React application. You should wrap your application (or the relevant part of your component tree) with the provider exported from this module to enable WebSocket functionality in child components.

    import { NextWsProvider } from './client';
    
    function App() {
      return (
        <NextWsProvider url="ws://localhost:8080">
          <YourComponent />
        </NextWsProvider>
      );
    }