dev-server.mjs
Rama main · réplica de lectura del repositorio original.
/**
* Custom Next.js dev server with WebSocket proxy.
*
* Problem: Next.js rewrites only handle HTTP, not WebSocket upgrade.
* In dev mode the frontend runs on :3001 while the Go backend listens on
* :8080. Browsers that reach the frontend via SSH tunnel, LAN IP, or
* localhost all need WebSocket to work transparently on the *same* origin
* as the page — otherwise the connection fails or gets blocked by origin
* checks.
*
* This server wraps `next dev` and intercepts `upgrade` requests for `/ws`
* to proxy them to the Go backend. No extra npm dependencies required.
*/
import { createServer, request as httpRequest } from "node:http"
import next from "next"
const port = parseInt(process.env.PORT || process.argv[2] || "3001", 10)
const goPort = parseInt(process.env.NEXT_PUBLIC_GO_PORT || "8080", 10)
const hostname = "0.0.0.0"
const app = next({ dev: true, hostname, port })
await app.prepare()
const handle = app.getRequestHandler()
const handleUpgrade = app.getUpgradeHandler()
const server = createServer((req, res) => handle(req, res))
server.on("upgrade", (req, socket, head) => {
// Only proxy the /ws path; delegate everything else (e.g. HMR at
// /_next/webpack-hmr) to Next.js so Turbopack hot-reload keeps working.
if (!req.url?.startsWith("/ws")) {
handleUpgrade(req, socket, head)
return
}
const proxyReq = httpRequest({
hostname: "localhost",
port: goPort,
path: req.url,
method: req.method,
headers: {
...req.headers,
// Forward the real client host so the backend can log it.
"x-forwarded-host": req.headers.host,
},
})
proxyReq.on("upgrade", (proxyRes, proxySocket, proxyHead) => {
// Relay the 101 Switching Protocols response.
const statusLine = `HTTP/1.1 101 Switching Protocols\r\n`
const headers = Object.entries(proxyRes.headers)
.map(([k, v]) => `${k}: ${v}`)
.join("\r\n")
socket.write(statusLine + headers + "\r\n\r\n")
if (proxyHead.length) socket.write(proxyHead)
proxySocket.pipe(socket)
socket.pipe(proxySocket)
proxySocket.on("error", () => socket.destroy())
socket.on("error", () => proxySocket.destroy())
})
// If the backend responds with a normal HTTP error (e.g. 401) instead of
// upgrading, forward that error and close the socket.
proxyReq.on("response", (proxyRes) => {
const statusLine = `HTTP/1.1 ${proxyRes.statusCode} ${proxyRes.statusMessage}\r\n`
const headers = Object.entries(proxyRes.headers)
.map(([k, v]) => `${k}: ${v}`)
.join("\r\n")
socket.write(statusLine + headers + "\r\n\r\n")
proxyRes.pipe(socket)
})
proxyReq.on("error", () => {
socket.destroy()
})
if (head.length) proxyReq.write(head)
proxyReq.end()
})
server.listen(port, hostname, () => {
console.log(`> Dev server ready on http://${hostname}:${port}`)
console.log(`> WebSocket /ws proxied to Go backend on :${goPort}`)
})