Skip to content

Commit 9e8b626

Browse files
timneutkensnatew
authored andcommitted
Add support for async fn / promise in next.config.js/.mjs (vercel#33662)
- Add support for async function / promise export in next.config.js/.mjs - Update docs Adds support for https://twitter.com/timneutkens/status/1486075973204422665 But also the simpler version: ```js module.exports = async () => { return { basePath: '/docs' } } ``` ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` ## Feature - [x] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [x] Documentation added ## Documentation / Examples - [ ] Make sure the linting passes by running `yarn lint`
1 parent a27a666 commit 9e8b626

File tree

7 files changed

+87
-32
lines changed

7 files changed

+87
-32
lines changed

docs/api-reference/next.config.js/introduction.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,20 @@ module.exports = (phase, { defaultConfig }) => {
4848
}
4949
```
5050

51+
Since Next.js 12.0.10, you can use an async function:
52+
53+
```js
54+
module.exports = async (phase, { defaultConfig }) => {
55+
/**
56+
* @type {import('next').NextConfig}
57+
*/
58+
const nextConfig = {
59+
/* config options here */
60+
}
61+
return nextConfig
62+
}
63+
```
64+
5165
`phase` is the current context in which the configuration is loaded. You can see the [available phases](https://github.com/vercel/next.js/blob/canary/packages/next/shared/lib/constants.ts#L1-L5). Phases can be imported from `next/constants`:
5266

5367
```js

errors/promise-in-next-config.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ module.exports = {
1414

1515
#### Possible Ways to Fix It
1616

17-
Check your `next.config.js` for `async` or `return Promise`
17+
In Next.js versions above `12.0.10`, `module.exports = async () =>` is supported.
18+
19+
For older versions, you can check your `next.config.js` for `async` or `return Promise`.
1820

1921
Potentially a plugin is returning a `Promise` from the webpack function.

packages/next/server/config-shared.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -466,15 +466,10 @@ export const defaultConfig: NextConfig = {
466466
},
467467
}
468468

469-
export function normalizeConfig(phase: string, config: any) {
469+
export async function normalizeConfig(phase: string, config: any) {
470470
if (typeof config === 'function') {
471471
config = config(phase, { defaultConfig })
472-
473-
if (typeof config.then === 'function') {
474-
throw new Error(
475-
'> Promise returned in next config. https://nextjs.org/docs/messages/promise-in-next-config'
476-
)
477-
}
478472
}
479-
return config
473+
// Support `new Promise` and `async () =>` as return values of the config export
474+
return await config
480475
}

packages/next/server/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -585,7 +585,7 @@ export default async function loadConfig(
585585
)
586586
throw err
587587
}
588-
const userConfig = normalizeConfig(
588+
const userConfig = await normalizeConfig(
589589
phase,
590590
userConfigModule.default || userConfigModule
591591
)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { createNext } from 'e2e-utils'
2+
import { NextInstance } from 'test/lib/next-modes/base'
3+
import { renderViaHTTP } from 'next-test-utils'
4+
5+
describe('async export', () => {
6+
let next: NextInstance
7+
8+
beforeAll(async () => {
9+
next = await createNext({
10+
files: {
11+
'pages/index.js': `
12+
export default function Page() {
13+
return <p>hello world</p>
14+
}
15+
`,
16+
'next.config.js': `
17+
module.exports = async () => {
18+
return {
19+
basePath: '/docs'
20+
}
21+
}
22+
`,
23+
},
24+
dependencies: {},
25+
})
26+
})
27+
afterAll(() => next.destroy())
28+
29+
it('should work', async () => {
30+
const html = await renderViaHTTP(next.url, '/docs')
31+
expect(html).toContain('hello world')
32+
})
33+
})
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { createNext } from 'e2e-utils'
2+
import { NextInstance } from 'test/lib/next-modes/base'
3+
import { renderViaHTTP } from 'next-test-utils'
4+
5+
describe('promise export', () => {
6+
let next: NextInstance
7+
8+
beforeAll(async () => {
9+
next = await createNext({
10+
files: {
11+
'pages/index.js': `
12+
export default function Page() {
13+
return <p>hello world</p>
14+
}
15+
`,
16+
'next.config.js': `
17+
module.exports = new Promise((resolve) => {
18+
resolve({
19+
basePath: '/docs'
20+
})
21+
})
22+
`,
23+
},
24+
dependencies: {},
25+
})
26+
})
27+
afterAll(() => next.destroy())
28+
29+
it('should work', async () => {
30+
const html = await renderViaHTTP(next.url, '/docs')
31+
expect(html).toContain('hello world')
32+
})
33+
})

test/integration/config-promise-error/test/index.test.js

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,28 +9,6 @@ const appDir = join(__dirname, '..')
99
describe('Promise in next config', () => {
1010
afterEach(() => fs.remove(join(appDir, 'next.config.js')))
1111

12-
it('should throw error when a promise is return on config', async () => {
13-
fs.writeFile(
14-
join(appDir, 'next.config.js'),
15-
`
16-
module.exports = (phase, { isServer }) => {
17-
return new Promise((resolve) => {
18-
resolve({ target: 'serverless' })
19-
})
20-
}
21-
`
22-
)
23-
24-
const { stderr, stdout } = await nextBuild(appDir, undefined, {
25-
stderr: true,
26-
stdout: true,
27-
})
28-
29-
expect(stderr + stdout).toMatch(
30-
/Error: > Promise returned in next config\. https:\/\//
31-
)
32-
})
33-
3412
it('should warn when a promise is returned on webpack', async () => {
3513
fs.writeFile(
3614
join(appDir, 'next.config.js'),

0 commit comments

Comments
 (0)