collaborative-editormap.setIfUndefined for document creation — eliminates TOCTOU race conditionsy-websocket@2.x bundles both client and server utils with yjs@^13 compatibilityws package already installed in GROWI; Express HTTP server supports adding WebSocket upgrade alongside Socket.IOy-websocket@2.xsync event + resyncInterval meets all requirements/yjs//yjs/{pageId}/socket.io/ path or Express routespage:{pageId}) to broadcast awareness updates to non-editor componentsio.in(roomName).emit() for awareness events, bridging from y-websocket awarenessdestroyUpgrade settingSocket.IO's engine.io v6 defaults destroyUpgrade: true in its attach() method. This causes engine.io to destroy all non-Socket.IO upgrade requests after a 1-second timeout. The Socket.IO server must be configured with destroyUpgrade: false to allow /yjs/ WebSocket handshakes to succeed.
Next.js's NextCustomServer.upgradeHandler registers an upgrade listener on the HTTP server. When the Yjs async handler yields at its first await, Next.js's synchronous handler runs and calls socket.end() for unrecognized paths. The guardSocket pattern temporarily replaces socket.end()/socket.destroy() with no-ops before the first await, restoring them after auth completes.
prependListener cannot solve this — it only changes listener order, cannot prevent subsequent listeners from executingProvider creation and awareness event handlers must be placed outside setProvider(() => { ... }) functional state updaters. If inside, awareness.setLocalStateField() triggers synchronous awareness events that update other components during render. All side effects go in the useEffect body; setProvider(_provider) is called with a plain value.
y-websocket does NOT await bindState before sending sync messages. However, within bindState itself, the ordering is guaranteed: persistence load → YDocStatus check → syncYDoc → awareness registration. This consolidation is intentional.