'How can I run a websocket server in next js custom server in dev mode
Assuming I want to run a custom next js server, and to accept websocket connections on that same server, how can I avoid clobbering the next js dev server hot reloading which is also using websockets on the same server...
const { createServer } = require('http')
const WebSocket = require("ws")
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = createServer((req, res) => handle(req, res, parse(req.url, true)))
// pass the same server instance that is used by next js to the websocket server
const wss = new WebSocket.Server({ server })
wss.on("connection", async function connection(ws) {
console.log('incoming connection', ws);
ws.onclose = () => {
console.log('connection closed', wss.clients.size);
};
});
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port} and ws://localhost:${port}`)
})
})
I believe this server should work in the production built version, so that the websocket server is created on the same server instance used to handle next js requests, but when I try to do this, the hot module reloading stops working, and errors appear in the chrome dev tools console because websocket connections it expects to be handled by webpack are now being handled by my custom websocket server.
How can I somehow route websocket connections for dev server to next and webpack and others to my own handler?
I know I can run my websocket server on another port, but I want to run it on the same server instance and same port as next js.
Solution 1:[1]
So the trick is to create a websocket server with noServer property set to true, and then listen to the server upgrade event, and depending on the pathname, do nothing to allow next js to do it's thing, or pass the request on to the websocket server we created...
const wss = new WebSocket.Server({ noServer: true })
server.on('upgrade', function (req, socket, head) {
const { pathname } = parse(req.url, true);
if (pathname !== '/_next/webpack-hmr') {
wss.handleUpgrade(req, socket, head, function done(ws) {
wss.emit('connection', ws, req);
});
}
});
... all together something like this ...
const { createServer } = require('http')
const WebSocket = require("ws")
const { parse } = require('url')
const next = require('next')
const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev })
const handle = app.getRequestHandler()
app.prepare().then(() => {
const server = createServer((req, res) => handle(req, res, parse(req.url, true)))
const wss = new WebSocket.Server({ noServer: true })
wss.on("connection", async function connection(ws) {
console.log('incoming connection', ws);
ws.onclose = () => {
console.log('connection closed', wss.clients.size);
};
});
server.on('upgrade', function (req, socket, head) {
const { pathname } = parse(req.url, true);
if (pathname !== '/_next/webpack-hmr') {
wss.handleUpgrade(req, socket, head, function done(ws) {
wss.emit('connection', ws, req);
});
}
});
server.listen(port, (err) => {
if (err) throw err
console.log(`> Ready on http://localhost:${port} and ws://localhost:${port}`)
})
})
Solution 2:[2]
Here is my answer to create a webSocket server on Next.js by using an api route instead of creating a custom server.
/pages/api/websocketserverinit.js:
import { WebSocketServer } from 'ws';
const SocketHandler = async (req, res) => {
if (res.socket.server.wss) {
console.log('Socket is already running')
} else {
console.log('Socket is initializing')
const server = res.socket.server
const wss = new WebSocketServer({ noServer: true })
res.socket.server.wss = wss
server.on('upgrade', (req, socket, head) => {
console.log("upgrade", req.url)
if (!req.url.includes('/_next/webpack-hmr')) {
wss.handleUpgrade(req, socket, head, (ws) => {
wss.emit('connection', ws, req);
});
}
});
wss.on('connection', (ws)=> {
console.log("connection", ws);
ws.on('message', (data) => {
console.log('received: %s', data);
})
ws.send('something');
});
}
res.end()
}
export default SocketHandler
You will have to call the api route to start the websocket server from the client (or server init script):
fetch("http://localhost:3000/api/websocketserverinit")
And then connect to it:
const ws = new WebSocket("ws://localhost:3000")
Not super nice, but may be useful in some case
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Billy Moon |
| Solution 2 | math_lab3.ca |
