Skip to content

Commit 643447e

Browse files
wbinnssmithstyfleijjkwyattjoh
authored
Allow port 0 in next dev and next start (vercel#40118)
This addresses a bug where invoking `next dev` or `next start` with `--port 0` would fall back to the default port of 3000 instead of binding to port 0 (which typically results in the operating system assigning a free port). I couldn't find a straightforward way of adding a test for next-start. It looks like we could add a similar test as for dev, but would need to generate a built project to serve. Manual test plan for `next start`: ``` $ ./packages/next/dist/bin/next start --port 0 ready - started server on 0.0.0.0:53508, url: http://localhost:53508 ``` ## Bug - [ ] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Documentation / Examples - [x] Make sure the linting passes by running `pnpm lint` - [ ] The examples guidelines are followed from [our contributing doc](https://github.com/vercel/next.js/blob/canary/contributing.md#adding-examples) Co-authored-by: Steven <[email protected]> Co-authored-by: JJ Kasper <[email protected]> Co-authored-by: Wyatt Johnson <[email protected]>
1 parent 92aafcb commit 643447e

File tree

6 files changed

+52
-19
lines changed

6 files changed

+52
-19
lines changed

packages/next/cli/next-dev.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import arg from 'next/dist/compiled/arg/index.js'
33
import { existsSync, watchFile } from 'fs'
44
import { startServer } from '../server/lib/start-server'
5-
import { printAndExit } from '../server/lib/utils'
5+
import { getPort, printAndExit } from '../server/lib/utils'
66
import * as Log from '../build/output/log'
77
import { startedDevelopmentServer } from '../build/output'
88
import { cliCommand } from '../lib/commands'
@@ -75,16 +75,11 @@ const nextDev: cliCommand = (argv) => {
7575
)
7676
}
7777
}
78-
const allowRetry = !args['--port']
79-
let port: number =
80-
args['--port'] || (process.env.PORT && parseInt(process.env.PORT)) || 3000
8178

82-
// we allow the server to use a random port while testing
83-
// instead of attempting to find a random port and then hope
84-
// it doesn't become occupied before we leverage it
85-
if (process.env.__NEXT_FORCED_PORT) {
86-
port = parseInt(process.env.__NEXT_FORCED_PORT, 10) || 0
87-
}
79+
const port = getPort(args)
80+
// If neither --port nor PORT were specified, it's okay to retry new ports.
81+
const allowRetry =
82+
args['--port'] === undefined && process.env.PORT === undefined
8883

8984
// We do not set a default host value here to prevent breaking
9085
// some set-ups that rely on listening on other interfaces

packages/next/cli/next-start.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import arg from 'next/dist/compiled/arg/index.js'
44
import { startServer } from '../server/lib/start-server'
5-
import { printAndExit } from '../server/lib/utils'
5+
import { getPort, printAndExit } from '../server/lib/utils'
66
import * as Log from '../build/output/log'
77
import isError from '../lib/is-error'
88
import { getProjectDir } from '../lib/get-project-dir'
@@ -52,13 +52,8 @@ const nextStart: cliCommand = (argv) => {
5252
}
5353

5454
const dir = getProjectDir(args._[0])
55-
let port: number =
56-
args['--port'] || (process.env.PORT && parseInt(process.env.PORT)) || 3000
5755
const host = args['--hostname'] || '0.0.0.0'
58-
59-
if (process.env.__NEXT_FORCED_PORT) {
60-
port = parseInt(process.env.__NEXT_FORCED_PORT, 10) || 0
61-
}
56+
const port = getPort(args)
6257

6358
const keepAliveTimeoutArg: number | undefined = args['--keepAliveTimeout']
6459
if (

packages/next/server/lib/utils.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import type arg from 'next/dist/compiled/arg/index.js'
2+
13
export function printAndExit(message: string, code = 1) {
24
if (code === 0) {
35
console.log(message)
@@ -12,3 +14,16 @@ export function getNodeOptionsWithoutInspect() {
1214
const NODE_INSPECT_RE = /--inspect(-brk)?(=\S+)?( |$)/
1315
return (process.env.NODE_OPTIONS || '').replace(NODE_INSPECT_RE, '')
1416
}
17+
18+
export function getPort(args: arg.Result<arg.Spec>): number {
19+
if (typeof args['--port'] === 'number') {
20+
return args['--port']
21+
}
22+
23+
const parsed = process.env.PORT && parseInt(process.env.PORT, 10)
24+
if (typeof parsed === 'number' && !Number.isNaN(parsed)) {
25+
return parsed
26+
}
27+
28+
return 3000
29+
}

test/integration/cli/test/index.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,34 @@ describe('CLI Usage', () => {
178178
expect(output).toMatch(new RegExp(`http://localhost:${port}`))
179179
})
180180

181+
test('--port 0', async () => {
182+
const output = await runNextCommandDev([dir, '--port', '0'], true)
183+
const matches = /on 0.0.0.0:(\d+)/.exec(output)
184+
expect(matches).not.toBe(null)
185+
186+
const port = parseInt(matches[1])
187+
// Regression test: port 0 was interpreted as if no port had been
188+
// provided, falling back to 3000.
189+
expect(port).not.toBe(3000)
190+
191+
expect(output).toMatch(new RegExp(`http://localhost:${port}`))
192+
})
193+
194+
test('PORT=0', async () => {
195+
const output = await runNextCommandDev([dir], true, {
196+
env: { PORT: 0 },
197+
})
198+
const matches = /on 0.0.0.0:(\d+)/.exec(output)
199+
expect(matches).not.toBe(null)
200+
201+
const port = parseInt(matches[1])
202+
// Regression test: port 0 was interpreted as if no port had been
203+
// provided, falling back to 3000.
204+
expect(port).not.toBe(3000)
205+
206+
expect(output).toMatch(new RegExp(`http://localhost:${port}`))
207+
})
208+
181209
test("NODE_OPTIONS='--inspect'", async () => {
182210
// this test checks that --inspect works by launching a single debugger for the main Next.js process,
183211
// not for its subprocesses

test/lib/next-modes/next-dev.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ export class NextDevInstance extends NextInstance {
3737
...process.env,
3838
...this.env,
3939
NODE_ENV: '' as any,
40+
PORT: this.forcedPort || '0',
4041
__NEXT_TEST_MODE: '1',
41-
__NEXT_FORCED_PORT: this.forcedPort || '0',
4242
__NEXT_TEST_WITH_DEVTOOL: '1',
4343
},
4444
})

test/lib/next-modes/next-start.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ export class NextStartInstance extends NextInstance {
4848
...process.env,
4949
...this.env,
5050
NODE_ENV: '' as any,
51+
PORT: this.forcedPort || '0',
5152
__NEXT_TEST_MODE: '1',
52-
__NEXT_FORCED_PORT: this.forcedPort || '0',
5353
},
5454
}
5555
let buildArgs = ['yarn', 'next', 'build']

0 commit comments

Comments
 (0)