/** * Dev launcher — runs `next dev` bound to all interfaces (so the site is * reachable from other devices on your LAN) and rewrites Next's * "http://0.0.0.0:3000" line to the machine's real LAN IP, so the terminal * shows a usable Network URL right next to Local: * * - Local: http://localhost:3000 * - Network: http://192.168.x.x:3000 * * It also auto-increments the port (3000 → 3001 → …) when the desired port is * already in use — including by another project bound only to localhost, which * a plain `next dev -H 0.0.0.0` fails to detect on Windows. Override the base * port with PORT=4000 npm run dev. */ import { spawn } from "node:child_process"; import net from "node:net"; import os from "node:os"; import path from "node:path"; const BASE_PORT = Number(process.env.PORT) || 3000; const nextBin = path.join(process.cwd(), "node_modules", "next", "dist", "bin", "next"); /** First non-internal IPv4 address (skips loopback + link-local). */ function lanIp() { for (const ifaces of Object.values(os.networkInterfaces())) { for (const net of ifaces || []) { const isV4 = net.family === "IPv4" || net.family === 4; if (isV4 && !net.internal && !net.address.startsWith("169.254.")) return net.address; } } return null; } /** * True if something is already listening on the port. We *connect* (rather * than try to bind) so we catch listeners on any address — including a project * bound only to 127.0.0.1, which a 0.0.0.0 bind test would miss on Windows. */ function portInUse(port) { return new Promise((resolve) => { const socket = net.connect({ host: "127.0.0.1", port, timeout: 500 }); const finish = (inUse) => { socket.destroy(); resolve(inUse); }; socket.once("connect", () => finish(true)); socket.once("timeout", () => finish(false)); socket.once("error", () => finish(false)); // ECONNREFUSED → free }); } async function findFreePort(base) { let port = base; while (await portInUse(port)) port += 1; return port; } const ip = lanIp(); const port = await findFreePort(BASE_PORT); if (port !== BASE_PORT) { console.log(`\n ⚠ Port ${BASE_PORT} is in use — using ${port} instead.`); } const child = spawn(process.execPath, [nextBin, "dev", "-H", "0.0.0.0", "-p", String(port)], { stdio: ["inherit", "pipe", "inherit"], env: process.env, }); // Swap Next's "0.0.0.0" for the real LAN IP so the Network URL is clickable. child.stdout.on("data", (chunk) => { const text = chunk.toString(); process.stdout.write(ip ? text.split("0.0.0.0").join(ip) : text); }); const forward = (sig) => child.kill(sig); process.on("SIGINT", () => forward("SIGINT")); process.on("SIGTERM", () => forward("SIGTERM")); child.on("exit", (code) => process.exit(code ?? 0));