Skip to content

Conversation

@0-don
Copy link

@0-don 0-don commented Oct 12, 2025

Summary by CodeRabbit

  • New Features
    • Multi‑instance WebSocket adapter with route‑scoped broadcasting, client lifecycle handling and graceful shutdown.
  • Documentation
    • Added a Redis‑backed example app with a simple chat UI and WebSocket endpoint demonstrating adapter usage.
  • Tests
    • End‑to‑end tests validating cross‑instance messaging, reconnection and multi‑client behaviour.
    • New CI workflow to run adapter tests and capture failure reports.
  • Chores
    • Added minor release changelog entry.

✏️ Tip: You can customize this high-level summary in your review settings.

@0-don 0-don requested a review from k0d13 as a code owner October 12, 2025 21:40
@changeset-bot
Copy link

changeset-bot bot commented Oct 12, 2025

🦋 Changeset detected

Latest commit: 24ad959

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions bot added dependencies Updates or changes related to project dependencies server Modifications or updates to server-handling code patches Updates or fixes related to Next.js patches examples Updates or additions to example apps tests Modifications, additions, or fixes related to testing labels Oct 12, 2025
@coderabbitai
Copy link

coderabbitai bot commented Oct 12, 2025

Walkthrough

Adds an Adapter interface and plumbing for multi‑instance WebSocket pub/sub, updates setupWebSocketServer to accept an optional adapter, exposes adapter APIs and globals, patches server init to pass an adapter, and adds a Redis-backed example, tests, CI workflow, and workspace/dependency changes.

Changes

Cohort / File(s) Summary
Changeset
\.changeset/yellow-pugs-melt.md
Adds changelog entry announcing WebSocket adapter support for multi‑instance deployments.
CI: Redis adapter tests
.github/workflows/redis-adapter-tests.yml
New GitHub Actions workflow to run Redis‑backed Playwright tests with Redis service, build, pnpm caching, and artifact upload on failure.
Redis adapter example app
examples/redis-adapter/app/(simple)/api/ws/route.ts, examples/redis-adapter/app/(simple)/page.tsx, examples/redis-adapter/app/layout.tsx, examples/redis-adapter/global.js, examples/redis-adapter/next-env.d.ts, examples/redis-adapter/package.json, examples/redis-adapter/server.ts, examples/redis-adapter/tsconfig.json
Adds Next.js example implementing a RedisAdapter: WebSocket route with GET & UPGRADE handlers, client page, layout, global AsyncLocalStorage binding, TS declarations, package manifest, custom server bootstrap using Redis pub/sub.
Workspace and deps
package.json, pnpm-workspace.yaml
Adds ioredis to root dependencies/devDependencies and to the workspace catalog; example package.json added.
Playwright config
playwright.config.ts
Minor comment added to webServer config (no behavioural change).
Core adapter feature
src/server/helpers/adapter.ts, src/server/index.ts, src/server/persistent.ts, src/server/setup.ts, src/patches/patch-1.ts
Introduces Adapter interface; exports Adapter and SetupOptions; adds global adapter persistence (getAdapter/setAdapter/useAdapter); updates setupWebSocketServer to accept optional adapter and implement route‑scoped pub/sub plumbing; patch passes adapter into server setup.
Tests: Redis adapter
tests/redis-adapter.test.ts
Adds Playwright tests that spawn two server instances with shared Redis, validating cross‑instance broadcast, reconnection handling, and cleanup helpers.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Dev as Dev / Custom Server
  participant Next as NextNodeServer
  participant HTTP as HTTP Server
  participant WSS as WebSocketServer
  participant Ad as Adapter (Redis pub/sub)

  Dev->>Ad: new RedisAdapter(REDIS_URL)
  Dev->>Next: create NextNodeServer()
  Dev->>WSS: create WebSocketServer(noServer)
  Dev->>Next: setupWebSocketServer(Next, { adapter: Ad })
  Dev->>HTTP: httpServer.listen(PORT)

  Note over Next,WSS: Upgrade handler registers route‑scoped client sets and adapter subscriptions
Loading
sequenceDiagram
  autonumber
  participant C1 as Client (Instance A)
  participant WS1 as WS Server (Inst A)
  participant Ad as Adapter (Redis)
  participant WS2 as WS Server (Inst B)
  participant C2 as Client (Instance B)

  C1->>WS1: send(message, room)
  WS1->>Ad: broadcast(room, message)
  Ad-->>WS1: onMessage(room, message)
  Ad-->>WS2: onMessage(room, message)
  WS1-->>C1: deliver(message)
  WS2-->>C2: deliver(message)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Focus areas:
    • src/server/setup.ts — route client sets, adapter message handling, upgrade and cleanup logic.
    • examples/redis-adapter/server.ts — RedisAdapter publish/subscribe, serialization, shutdown.
    • src/patches/patch-1.ts — patch injection and changed setupWebSocketServer signature.
    • tests & CI — orchestration, process lifecycle and flaky timing concerns.

Possibly related issues

Possibly related PRs

Poem

I twitch my ears at sockets’ song,
Across the nodes the packets throng—
A hop through Redis, quick and neat,
Rooms connected, messages meet.
Two burrows sing, one chorus strong. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main changes: introducing WebSocket adapter support for multi-instance deployments and enhancing the setup options API, which are the core additions across the codebase.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@0-don
Copy link
Author

0-don commented Oct 12, 2025

should be good now, tests are skipped if redis is not detected, it will be still tested via github actions

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (8)
examples/redis-adapter/global.js (1)

1-1: Consider using a more defensive approach for the global binding.

Directly assigning to globalThis.AsyncLocalStorage can conflict with user code that may already define this property or rely on AsyncLocalStorage from a different source. The require statement also lacks error handling.

Apply this diff to add a conditional check and error handling:

-globalThis.AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage;
+if (!globalThis.AsyncLocalStorage) {
+  try {
+    globalThis.AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage;
+  } catch (err) {
+    console.error('Failed to load AsyncLocalStorage:', err);
+  }
+}
examples/redis-adapter/app/layout.tsx (1)

1-17: Consider extracting the duplicated layout to a shared module.

This Layout component is identical to those in examples/chat-room/app/layout.tsx, examples/base-path/app/layout.tsx, and examples/custom-server/app/layout.tsx. Extracting it to a shared location (e.g., examples/_shared/src/layout.tsx) would eliminate duplication and simplify maintenance.

examples/redis-adapter/app/(simple)/page.tsx (1)

6-8: Use protocol-aware WebSocket URL construction.

The hardcoded ws:// protocol won't work correctly in production environments using HTTPS, where wss:// is required. Use window.location.protocol to construct the appropriate WebSocket URL.

Apply this diff to make the WebSocket URL protocol-aware:

   const [messages, sendMessage] = useMessaging(
-    () => `ws://${window.location.host}/api/ws`,
+    () => {
+      const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
+      return `${protocol}//${window.location.host}/api/ws`;
+    },
   );
tests/redis-adapter.test.ts (1)

91-112: Replace fixed sleeps with Playwright’s auto-waiting expectations

Hard sleeps make tests flaky/slow. Prefer locator-based waits (toContainText/toHaveText), which auto-retry until assertions pass.

Example change (apply similarly to other sleeps):

// Instead of:
await page1.click('button[type=submit]');
await sleep(1500);
const page2LastMessage = await page2.textContent('li:last-child');
expect(page2LastMessage).toContain('Hello from instance 1!');

// Prefer:
await page1.click('button[type=submit]');
await expect(page2.locator('li:last-child')).toContainText('Hello from instance 1!');

Also consider waiting for welcome text directly:

await expect(page1.locator('li:first-child')).toContainText('instance-1');
await expect(page2.locator('li:first-child')).toContainText('instance-2');

Also applies to: 148-167, 184-207

src/server/setup.ts (2)

147-151: Clean up empty route sets to prevent unbounded Map growth

When the last client leaves, delete the entry to avoid memory creep in long-lived servers with many rooms.

Apply this diff:

-      client.once('close', () => {
-        if (adapter) {
-          routeClients.get(pathname)?.delete(client);
-        }
-      });
+      client.once('close', () => {
+        if (adapter) {
+          const set = routeClients.get(pathname);
+          if (!set) return;
+          set.delete(client);
+          if (set.size === 0) routeClients.delete(pathname);
+        }
+      });

51-53: No per-room unsubscribe path → risk of long‑lived subscriptions

adapter.onMessage is registered per room with no way to unsubscribe when no clients remain. Over time (dynamic routes) this can leak subs on the adapter backend.

Consider extending the Adapter interface with offMessage(room, handler) and calling it when a room becomes empty. Alternatively, have onMessage return a disposer you can invoke on cleanup. This keeps Redis (or any backend) subscription cardinality bounded.

Also applies to: 94-111, 157-164

examples/redis-adapter/server.ts (2)

27-32: Avoid one 'message' listener per room; consolidate and route to handlers

Each onMessage call adds a new 'message' listener, which can lead to listener explosion as rooms grow. Prefer a single listener that dispatches by channel, and keep per-room handler sets.

Example refactor:

class RedisAdapter implements Adapter {
  private pub = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
  private sub = new Redis(process.env.REDIS_URL || 'redis://localhost:6379');
  private handlers = new Map<string, Set<(message: unknown) => void>>();
  private wired = false;

  private wireOnce() {
    if (this.wired) return;
    this.wired = true;
    this.sub.on('message', (channel: string, msg: string) => {
      const hs = this.handlers.get(channel);
      if (!hs) return;
      for (const h of hs) h(msg);
    });
  }

  onMessage(room: string, handler: (message: unknown) => void): void {
    this.wireOnce();
    if (!this.handlers.has(room)) this.handlers.set(room, new Set());
    this.handlers.get(room)!.add(handler);
    // Optionally await to surface errors and ensure sub is active before use
    void this.sub.subscribe(room);
  }
}

Optionally, return a disposer to remove the handler and unsubscribe when the set becomes empty.


27-29: Consider awaiting subscribe to surface errors

this.sub.subscribe(room) returns a promise; awaiting helps fail fast if the subscription cannot be established.

await this.sub.subscribe(room);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 670e6aa and 1a1a18c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (19)
  • .changeset/yellow-pugs-melt.md (1 hunks)
  • .github/workflows/redis-adapter-tests.yml (1 hunks)
  • examples/redis-adapter/app/(simple)/api/ws/route.ts (1 hunks)
  • examples/redis-adapter/app/(simple)/page.tsx (1 hunks)
  • examples/redis-adapter/app/layout.tsx (1 hunks)
  • examples/redis-adapter/global.js (1 hunks)
  • examples/redis-adapter/next-env.d.ts (1 hunks)
  • examples/redis-adapter/package.json (1 hunks)
  • examples/redis-adapter/server.ts (1 hunks)
  • examples/redis-adapter/tsconfig.json (1 hunks)
  • package.json (1 hunks)
  • playwright.config.ts (1 hunks)
  • pnpm-workspace.yaml (1 hunks)
  • src/patches/patch-1.ts (1 hunks)
  • src/server/helpers/adapter.ts (1 hunks)
  • src/server/index.ts (1 hunks)
  • src/server/persistent.ts (1 hunks)
  • src/server/setup.ts (6 hunks)
  • tests/redis-adapter.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
examples/redis-adapter/app/layout.tsx (3)
examples/chat-room/app/layout.tsx (1)
  • Layout (1-17)
examples/base-path/app/layout.tsx (1)
  • Layout (1-17)
examples/custom-server/app/layout.tsx (1)
  • Layout (1-17)
.changeset/yellow-pugs-melt.md (1)
examples/base-path/app/(simple)/api/ws/route.ts (1)
  • UPGRADE (10-53)
examples/redis-adapter/app/(simple)/page.tsx (3)
examples/_shared/src/chat-room/messaging.ts (1)
  • useMessaging (11-64)
examples/_shared/src/chat-room/message-list.tsx (1)
  • MessageList (5-40)
examples/_shared/src/chat-room/message-submit.tsx (1)
  • MessageSubmit (8-48)
tests/redis-adapter.test.ts (1)
tests/custom-server.test.ts (5)
  • code (85-102)
  • test (57-103)
  • test (15-55)
  • code (74-83)
  • page1 (30-37)
src/server/persistent.ts (1)
src/server/index.ts (2)
  • getAdapter (5-5)
  • setAdapter (8-8)
src/server/setup.ts (1)
src/server/helpers/adapter.ts (1)
  • Adapter (28-47)
examples/redis-adapter/server.ts (1)
src/server/helpers/adapter.ts (1)
  • Adapter (28-47)
🔇 Additional comments (14)
playwright.config.ts (1)

33-33: LGTM!

The comment appropriately clarifies why the Redis adapter tests are not included in the webServer array, improving code maintainability.

examples/redis-adapter/next-env.d.ts (1)

1-6: LGTM!

Standard Next.js TypeScript declaration file. The reference directives and non-editable notice align with Next.js conventions.

.github/workflows/redis-adapter-tests.yml (1)

1-57: LGTM!

The workflow is well-structured with:

  • Proper Redis service configuration including health checks (lines 18-22)
  • Efficient browser installation (only Chromium at line 43)
  • Appropriate environment variable configuration (lines 47-49)
  • Sensible artifact retention policy (7 days at line 57)

The workflow effectively tests Redis adapter functionality in CI.

examples/redis-adapter/package.json (2)

1-20: Package configuration looks good.

The use of workspace and catalog specifiers is appropriate for the monorepo structure. Dependencies are correctly declared.


5-5: global.js presence and initialisation verified
The file examples/redis-adapter/global.js exists and correctly initialises globalThis.AsyncLocalStorage from node:async_hooks. No further action required.

.changeset/yellow-pugs-melt.md (1)

1-5: LGTM!

The changeset correctly identifies this as a minor version bump with an appropriate description of the new WebSocket adapter feature for multi-instance deployments.

pnpm-workspace.yaml (1)

10-10: LGTM!

Adding ioredis 5.8.1 to the workspace catalog is correct. Based on learnings, this is the latest stable version of ioredis with full TypeScript support and comprehensive Redis features including pub/sub required for the adapter implementation.

src/server/index.ts (1)

1-13: LGTM!

The new exports properly expose the Adapter interface and related accessors (getAdapter, setAdapter) along with SetupOptions type, establishing a clean public API for the WebSocket adapter functionality.

package.json (1)

72-72: ioredis should remain a devDependency

ioredis is only imported in example/test code (examples/redis-adapter/server.ts) and never used under src, so leaving it in devDependencies is correct.

src/patches/patch-1.ts (1)

24-25: LGTM!

The adapter retrieval and conditional passing logic is correct. Optional chaining ensures safe access, and the ternary properly constructs the options object only when an adapter is present.

src/server/persistent.ts (1)

96-120: LGTM!

The adapter accessor implementation follows the established pattern for other server components. The use of useGlobal with proper typing and JSDoc comments is consistent and correct.

examples/redis-adapter/app/(simple)/api/ws/route.ts (1)

10-11: Clarify the purpose of awaiting headers().

Line 11 awaits headers() but doesn't use the returned value. If this is intentional (e.g., to ensure async context is properly established), consider adding a comment explaining why. Otherwise, remove the unused await.

Is this await necessary for establishing the request context in Next.js WebSocket routes?

src/server/helpers/adapter.ts (1)

1-47: LGTM!

The Adapter interface is well-designed and thoroughly documented. The JSDoc example clearly demonstrates the intended usage pattern with Redis pub/sub, and the method signatures are appropriately typed for async operations.

src/server/setup.ts (1)

89-112: Verify and prevent duplicate broadcasts with adapter enabled

Route handlers may broadcast on client message, and the adapter’s onMessage rebroadcasts the same payload back to the originating instance—resulting in duplicated sends locally. Verify your UPGRADE/SOCKET handlers don’t perform local broadcasts when an adapter is present, or else:

  • Centralise all broadcasts via the adapter (documented contract for routes),
  • Add envelope-based de-dup (e.g. include an instanceId to suppress local echo), or
  • Introduce a SetupOptions flag to toggle local echo vs adapter relay.

@0-don
Copy link
Author

0-don commented Dec 2, 2025

@apteryxxyz is this feature missing something ?

@k0d13
Copy link
Owner

k0d13 commented Dec 3, 2025

See requested changes from coderabbit

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
tests/redis-adapter.test.ts (2)

59-64: Consider polling over fixed delays for more reliable test timing.

The additional 1000ms sleep after server readiness assumes WebSocket initialization time. While this may work, tests would be more reliable and potentially faster with polling logic (e.g., attempting a WebSocket connection and retrying with exponential backoff).


78-211: Consider replacing fixed sleep durations with polling for robustness.

The tests use fixed sleep() durations (1000ms for connections, 1500ms for message propagation) which can be fragile under load or slow Redis instances. More robust alternatives include:

  • Playwright's waitForSelector() with text matching
  • Custom polling with retry logic and timeout

Example:

// Instead of sleep(1500) after sending
await page.waitForSelector('li:last-child:has-text("Hello from instance 1!")', { timeout: 5000 });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1a1a18c and 1e84393.

📒 Files selected for processing (3)
  • examples/redis-adapter/tsconfig.json (1 hunks)
  • src/server/setup.ts (6 hunks)
  • tests/redis-adapter.test.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: apteryxxyz
Repo: apteryxxyz/next-ws PR: 150
File: .changeset/mk4lP2zgn0Nq9.md:1-5
Timestamp: 2025-11-30T18:15:16.883Z
Learning: In the apteryxxyz/next-ws repository, pull requests that bump Next.js version ranges (e.g., updating to 16.0.6) are created by an automated workflow that triggers when new Next.js versions are released. When reviewing such PRs, assume the Next.js version referenced has already been released and is available.
📚 Learning: 2025-08-22T12:23:23.227Z
Learnt from: apteryxxyz
Repo: apteryxxyz/next-ws PR: 113
File: src/server/helpers/module.ts:29-32
Timestamp: 2025-08-22T12:23:23.227Z
Learning: Next.js builds pages to CommonJS format, so using require() to load built pages via nextServer.getPagePath() is appropriate and ESM compatibility concerns don't apply in this context.

Applied to files:

  • src/server/setup.ts
📚 Learning: 2025-08-22T05:11:55.871Z
Learnt from: apteryxxyz
Repo: apteryxxyz/next-ws PR: 112
File: src/server/route/match.ts:3-9
Timestamp: 2025-08-22T05:11:55.871Z
Learning: In next-ws route matching, the route patterns passed to compileRoutePattern come from Next.js internal appPathRoutes (filesystem-based route definitions), which are developer-controlled. User input (requestPathname) is matched against these compiled regexes, not used to construct them, so ReDoS concerns don't apply to the regex construction itself.

Applied to files:

  • src/server/setup.ts
📚 Learning: 2025-08-22T12:10:01.850Z
Learnt from: apteryxxyz
Repo: apteryxxyz/next-ws PR: 113
File: src/patches/helpers/define.ts:3-4
Timestamp: 2025-08-22T12:10:01.850Z
Learning: TSUP bundler resolves path aliases like '~/' during compilation when creating single-file bundles, so alias configuration is not required in the build config for this use case.

Applied to files:

  • examples/redis-adapter/tsconfig.json
🧬 Code graph analysis (1)
src/server/setup.ts (1)
src/server/helpers/adapter.ts (1)
  • Adapter (28-47)
🔇 Additional comments (11)
examples/redis-adapter/tsconfig.json (1)

1-55: LGTM! Well-configured TypeScript setup for Next.js.

The configuration follows Next.js best practices with appropriate strict settings, Bundler module resolution, and safety checks. The compiler options are well-suited for a modern Next.js 15 project.

src/server/setup.ts (7)

16-18: Good addition of type-safe setup options.

The SetupOptions interface with optional adapter field provides clean configuration extensibility for multi-instance WebSocket support.


38-44: Clear differentiation between single and multi-instance modes.

The conditional logging appropriately communicates whether the adapter is configured, helping operators understand the deployment mode.


51-52: Appropriate route-scoped client tracking.

The routeClients map enables per-route adapter subscriptions and targeted broadcasting, which is correct for multi-instance pub/sub.


89-111: Well-structured adapter subscription with correct WebSocket state check.

The per-route subscription logic is efficient (subscribes once when first client connects) and the broadcast handler correctly uses WebSocket.OPEN constant to filter ready clients. Good use of optional chaining for defensive programming.


129-136: Correct message interception for cross-instance broadcasting.

Client messages are properly routed through the adapter with appropriate error handling, ensuring multi-instance message propagation.


147-151: Proper client cleanup on disconnect.

The close handler correctly removes disconnected clients from the route-specific set, preventing memory leaks.


157-164: Appropriate adapter lifecycle management.

The adapter cleanup on server close ensures graceful shutdown with proper error handling and logging.

tests/redis-adapter.test.ts (3)

11-27: Effective Redis availability check for conditional test execution.

The helper properly tests Redis connectivity with appropriate connection options and cleanup, allowing tests to skip gracefully when Redis is unavailable.


214-253: Well-implemented server readiness check.

The helper properly monitors stdout for the ready signal with appropriate timeout handling and cleanup, ensuring reliable server startup detection.


255-276: Properly handles process exit races with early exit check.

The helper correctly checks if the process has already exited before setting up listeners, avoiding the race condition where exit happens before the listener attaches. The use of once() and defensive SIGKILL logic ensure reliability.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/server/setup.ts (2)

4-5: Minor typing tidy‑up for routeClients

You already import WebSocket from ws, but routeClients is typed with Set<import('ws').WebSocket>. For consistency and easier future refactors, you could reuse the imported type:

-import { WebSocketServer, WebSocket } from 'ws';
+import { WebSocketServer, WebSocket } from 'ws';-  // Store route -> clients mapping for adapter broadcasts
-  const routeClients = new Map<string, Set<import('ws').WebSocket>>();
+  // Store route -> clients mapping for adapter broadcasts
+  const routeClients = new Map<string, Set<WebSocket>>();

Purely cosmetic, but keeps the typing story a bit neater.

Also applies to: 51-52


89-111: Consider cleaning up empty route client sets to avoid unbounded growth

The adapter integration and per‑route client tracking are nicely structured, and the adapter.close() hook on HTTP server shutdown is a good lifecycle safeguard. One small concern: when the last client for a pathname disconnects, you remove it from the Set but keep an empty Set in routeClients (and the adapter subscription remains active for that room). With many dynamic routes/rooms over time, this can lead to unbounded growth of empty entries.

You can cheaply clean up empty sets on close:

-      client.once('close', () => {
-        if (adapter) {
-          routeClients.get(pathname)?.delete(client);
-        }
-      });
+      client.once('close', () => {
+        if (!adapter) return;
+        const clients = routeClients.get(pathname);
+        if (!clients) return;
+        clients.delete(client);
+        if (clients.size === 0) {
+          routeClients.delete(pathname);
+        }
+      });

This keeps the in‑memory map bounded even for high‑churn dynamic rooms, while still playing nicely with your existing adapter.onMessage callback (which already copes with routeClients.get(pathname) returning undefined).

Also applies to: 129-136, 147-151, 157-164

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e84393 and 24ad959.

📒 Files selected for processing (3)
  • examples/redis-adapter/tsconfig.json (1 hunks)
  • src/server/setup.ts (6 hunks)
  • tests/redis-adapter.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/redis-adapter.test.ts
  • examples/redis-adapter/tsconfig.json
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: apteryxxyz
Repo: apteryxxyz/next-ws PR: 150
File: .changeset/mk4lP2zgn0Nq9.md:1-5
Timestamp: 2025-11-30T18:15:16.883Z
Learning: In the apteryxxyz/next-ws repository, pull requests that bump Next.js version ranges (e.g., updating to 16.0.6) are created by an automated workflow that triggers when new Next.js versions are released. When reviewing such PRs, assume the Next.js version referenced has already been released and is available.
📚 Learning: 2025-08-22T12:23:23.227Z
Learnt from: apteryxxyz
Repo: apteryxxyz/next-ws PR: 113
File: src/server/helpers/module.ts:29-32
Timestamp: 2025-08-22T12:23:23.227Z
Learning: Next.js builds pages to CommonJS format, so using require() to load built pages via nextServer.getPagePath() is appropriate and ESM compatibility concerns don't apply in this context.

Applied to files:

  • src/server/setup.ts
📚 Learning: 2025-08-22T05:11:55.871Z
Learnt from: apteryxxyz
Repo: apteryxxyz/next-ws PR: 112
File: src/server/route/match.ts:3-9
Timestamp: 2025-08-22T05:11:55.871Z
Learning: In next-ws route matching, the route patterns passed to compileRoutePattern come from Next.js internal appPathRoutes (filesystem-based route definitions), which are developer-controlled. User input (requestPathname) is matched against these compiled regexes, not used to construct them, so ReDoS concerns don't apply to the regex construction itself.

Applied to files:

  • src/server/setup.ts
🧬 Code graph analysis (1)
src/server/setup.ts (2)
src/server/index.ts (2)
  • SetupOptions (12-12)
  • Adapter (1-1)
src/server/helpers/adapter.ts (1)
  • Adapter (28-47)
🔇 Additional comments (1)
src/server/setup.ts (1)

16-18: Adapter options wiring looks good

The SetupOptions interface and optional options parameter cleanly introduce adapter injection while keeping the public API extendable; the conditional startup logging also reads clearly. No changes needed here.

Also applies to: 23-28, 38-44

@0-don
Copy link
Author

0-don commented Dec 4, 2025

@apteryxxyz finally!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Updates or changes related to project dependencies examples Updates or additions to example apps patches Updates or fixes related to Next.js patches server Modifications or updates to server-handling code tests Modifications, additions, or fixes related to testing

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants