Skip to content

Commit

Permalink
Ensure error message prints next.config.mjs (#30152)
Browse files Browse the repository at this point in the history
This PR ensures we print the correct error message for either `next.config.js` or `next.config.mjs`, whichever was detected.
  • Loading branch information
styfle committed Oct 21, 2021
1 parent 97f0752 commit 6f3e947
Show file tree
Hide file tree
Showing 10 changed files with 76 additions and 24 deletions.
15 changes: 14 additions & 1 deletion docs/api-reference/next.config.js/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: learn more about the configuration file used by Next.js to handle y

# next.config.js

For custom advanced behavior of Next.js, you can create a `next.config.js` in the root of your project directory (next to `package.json`).
For custom advanced configuration of Next.js, you can create a `next.config.js` or `next.config.mjs` file in the root of your project directory (next to `package.json`).

`next.config.js` is a regular Node.js module, not a JSON file. It gets used by the Next.js server and build phases, and it's not included in the browser build.

Expand All @@ -21,6 +21,19 @@ const nextConfig = {
module.exports = nextConfig
```

If you need [ECMAScript modules](https://nodejs.org/api/esm.html), you can use `next.config.mjs`:

```js
/**
* @type {import('next').NextConfig}
*/
const nextConfig = {
/* config options here */
}

export default nextConfig
```

You can also use a function:

```js
Expand Down
8 changes: 5 additions & 3 deletions errors/next-config-error.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

#### Why This Error Occurred

When attempting to load your `next.config.js` file an error occurred. This could be due to a syntax error or attempting to `require` a module that wasn't available.
When attempting to load your `next.config.js` or `next.config.mjs` file, an error occurred. This could be due to a syntax error or attempting to `require`/`import` a module that wasn't available.

#### Possible Ways to Fix It

See the error message in your terminal where you started `next` to see more context. The `next.config.js` file is not transpiled by Next.js currently so ensure only features supported by your current node.js version are being used.
See the error message in your terminal where you started `next` to see more context.

> Note: This config file is not transpiled by Next.js, so only use features supported by your current Node.js version.
### Useful Links

- [next.config.js documentation](https://nextjs.org/docs/api-reference/next.config.js/introduction)
- [node.js version feature chart](https://node.green/)
- [Node.js version feature chart](https://node.green/)
3 changes: 3 additions & 0 deletions packages/next/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,7 @@ export default async function build(
} = await staticCheckSpan.traceAsyncFn(async () => {
process.env.NEXT_PHASE = PHASE_PRODUCTION_BUILD

const configFileName = config.configFileName
const runtimeEnvConfig = {
publicRuntimeConfig: config.publicRuntimeConfig,
serverRuntimeConfig: config.serverRuntimeConfig,
Expand Down Expand Up @@ -793,6 +794,7 @@ export default async function build(
'/_error',
distDir,
isLikeServerless,
configFileName,
runtimeEnvConfig,
config.httpAgentOptions,
config.i18n?.locales,
Expand Down Expand Up @@ -859,6 +861,7 @@ export default async function build(
page,
distDir,
isLikeServerless,
configFileName,
runtimeEnvConfig,
config.httpAgentOptions,
config.i18n?.locales,
Expand Down
5 changes: 4 additions & 1 deletion packages/next/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -617,6 +617,7 @@ export async function getJsPageSizeInKb(
export async function buildStaticPaths(
page: string,
getStaticPaths: GetStaticPaths,
configFileName: string,
locales?: string[],
defaultLocale?: string
): Promise<
Expand Down Expand Up @@ -788,7 +789,7 @@ export async function buildStaticPaths(

if (entry.locale && !locales?.includes(entry.locale)) {
throw new Error(
`Invalid locale returned from getStaticPaths for ${page}, the locale ${entry.locale} is not specified in next.config.js`
`Invalid locale returned from getStaticPaths for ${page}, the locale ${entry.locale} is not specified in ${configFileName}`
)
}
const curLocale = entry.locale || defaultLocale || ''
Expand Down Expand Up @@ -817,6 +818,7 @@ export async function isPageStatic(
page: string,
distDir: string,
serverless: boolean,
configFileName: string,
runtimeEnvConfig: any,
httpAgentOptions: NextConfigComplete['httpAgentOptions'],
locales?: string[],
Expand Down Expand Up @@ -925,6 +927,7 @@ export async function isPageStatic(
} = await buildStaticPaths(
page,
mod.getStaticPaths!,
configFileName,
locales,
defaultLocale
))
Expand Down
6 changes: 3 additions & 3 deletions packages/next/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1130,7 +1130,7 @@ export default async function getBaseWebpackConfig(
...Object.keys(config.env).reduce((acc, key) => {
if (/^(?:NODE_.+)|^(?:__.+)$/i.test(key)) {
throw new Error(
`The key "${key}" under "env" in next.config.js is not allowed. https://nextjs.org/docs/messages/env-key-not-allowed`
`The key "${key}" under "env" in ${config.configFileName} is not allowed. https://nextjs.org/docs/messages/env-key-not-allowed`
)
}

Expand Down Expand Up @@ -1522,7 +1522,7 @@ export default async function getBaseWebpackConfig(

if (!webpackConfig) {
throw new Error(
'Webpack config is undefined. You may have forgot to return properly from within the "webpack" method of your next.config.js.\n' +
`Webpack config is undefined. You may have forgot to return properly from within the "webpack" method of your ${config.configFileName}.\n` +
'See more info here https://nextjs.org/docs/messages/undefined-webpack-config'
)
}
Expand Down Expand Up @@ -1726,7 +1726,7 @@ export default async function getBaseWebpackConfig(

if (foundTsRule) {
console.warn(
'\n@zeit/next-typescript is no longer needed since Next.js has built-in support for TypeScript now. Please remove it from your next.config.js and your .babelrc\n'
`\n@zeit/next-typescript is no longer needed since Next.js has built-in support for TypeScript now. Please remove it from your ${config.configFileName} and your .babelrc\n`
)
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/next/server/config-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type NextConfigComplete = Required<NextConfig> & {
typescript: Required<TypeScriptConfig>
configOrigin?: string
configFile?: string
configFileName: string
}

export interface I18NConfig {
Expand Down
18 changes: 10 additions & 8 deletions packages/next/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,11 @@ const experimentalWarning = execOnce(() => {
})

function assignDefaults(userConfig: { [key: string]: any }) {
const configFileName = userConfig.configFileName || 'next.config.js'
if (typeof userConfig.exportTrailingSlash !== 'undefined') {
console.warn(
chalk.yellow.bold('Warning: ') +
'The "exportTrailingSlash" option has been renamed to "trailingSlash". Please update your next.config.js.'
`The "exportTrailingSlash" option has been renamed to "trailingSlash". Please update your ${configFileName}.`
)
if (typeof userConfig.trailingSlash === 'undefined') {
userConfig.trailingSlash = userConfig.exportTrailingSlash
Expand All @@ -45,7 +46,7 @@ function assignDefaults(userConfig: { [key: string]: any }) {
if (typeof userConfig.experimental?.reactMode !== 'undefined') {
console.warn(
chalk.yellow.bold('Warning: ') +
'The experimental "reactMode" option has been replaced with "reactRoot". Please update your next.config.js.'
`The experimental "reactMode" option has been replaced with "reactRoot". Please update your ${configFileName}.`
)
if (typeof userConfig.experimental?.reactRoot === 'undefined') {
userConfig.experimental.reactRoot = ['concurrent', 'blocking'].includes(
Expand Down Expand Up @@ -353,14 +354,14 @@ function assignDefaults(userConfig: { [key: string]: any }) {

if (result.webpack5 === false) {
throw new Error(
'Webpack 4 is no longer supported in Next.js. Please upgrade to webpack 5 by removing "webpack5: false" from next.config.js. https://nextjs.org/docs/messages/webpack5'
`Webpack 4 is no longer supported in Next.js. Please upgrade to webpack 5 by removing "webpack5: false" from ${configFileName}. https://nextjs.org/docs/messages/webpack5`
)
}

if (result.experimental && 'nftTracing' in (result.experimental as any)) {
// TODO: remove this warning and assignment when we leave experimental phase
Log.warn(
`Experimental \`nftTracing\` has been renamed to \`outputFileTracing\`. Please update your next.config.js file accordingly.`
`Experimental \`nftTracing\` has been renamed to \`outputFileTracing\`. Please update your ${configFileName} file accordingly.`
)
result.experimental.outputFileTracing = (
result.experimental as any
Expand Down Expand Up @@ -538,6 +539,7 @@ export default async function loadConfig(

// If config file was found
if (path?.length) {
const configName = basename(path)
let userConfigModule: any

try {
Expand All @@ -546,9 +548,8 @@ export default async function loadConfig(
// with the `file://` protocol
userConfigModule = await import(pathToFileURL(path).href)
} catch (err) {
console.error(
chalk.red('Error:') +
' failed to load next.config.js, see more info here https://nextjs.org/docs/messages/next-config-error'
Log.error(
`Failed to load ${configName}, see more info here https://nextjs.org/docs/messages/next-config-error`
)
throw err
}
Expand All @@ -559,7 +560,7 @@ export default async function loadConfig(

if (Object.keys(userConfig).length === 0) {
Log.warn(
'Detected next.config.js, no exported configuration found. https://nextjs.org/docs/messages/empty-configuration'
`Detected ${configName}, no exported configuration found. https://nextjs.org/docs/messages/empty-configuration`
)
}

Expand Down Expand Up @@ -587,6 +588,7 @@ export default async function loadConfig(
return assignDefaults({
configOrigin: relative(dir, path),
configFile: path,
configFileName: configName,
...userConfig,
}) as NextConfigComplete
} else {
Expand Down
9 changes: 7 additions & 2 deletions packages/next/server/dev/next-dev-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -780,15 +780,20 @@ export default class DevServer extends Server {
// from waiting on them for the page to load in dev mode

const __getStaticPaths = async () => {
const { publicRuntimeConfig, serverRuntimeConfig, httpAgentOptions } =
this.nextConfig
const {
configFileName,
publicRuntimeConfig,
serverRuntimeConfig,
httpAgentOptions,
} = this.nextConfig
const { locales, defaultLocale } = this.nextConfig.i18n || {}

const paths = await this.staticPathsWorker.loadStaticPaths(
this.distDir,
pathname,
!this.renderOpts.dev && this._isLikeServerless,
{
configFileName,
publicRuntimeConfig,
serverRuntimeConfig,
},
Expand Down
1 change: 1 addition & 0 deletions packages/next/server/dev/static-paths-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export async function loadStaticPaths(
return buildStaticPaths(
pathname,
components.getStaticPaths,
config.configFileName,
locales,
defaultLocale
)
Expand Down
34 changes: 28 additions & 6 deletions test/integration/config-syntax-error/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,13 @@ import { join } from 'path'
import { nextBuild } from 'next-test-utils'

const appDir = join(__dirname, '..')
const nextConfig = join(appDir, 'next.config.js')
const nextConfigJS = join(appDir, 'next.config.js')
const nextConfigMJS = join(appDir, 'next.config.mjs')

describe('Invalid resolve alias', () => {
it('should show relevant error when webpack resolve alias is wrong', async () => {
describe('Invalid config syntax', () => {
it('should error when next.config.js contains syntax error', async () => {
await fs.writeFile(
nextConfig,
nextConfigJS,
`
module.exports = {
reactStrictMode: true,,
Expand All @@ -19,10 +20,31 @@ describe('Invalid resolve alias', () => {
const { stderr } = await nextBuild(appDir, undefined, {
stderr: true,
})
await fs.remove(nextConfig)
await fs.remove(nextConfigJS)

expect(stderr).toContain(
'Error: failed to load next.config.js, see more info here https://nextjs.org/docs/messages/next-config-error'
'error - Failed to load next.config.js, see more info here https://nextjs.org/docs/messages/next-config-error'
)
expect(stderr).toContain('SyntaxError')
})

it('should error when next.config.mjs contains syntax error', async () => {
await fs.writeFile(
nextConfigMJS,
`
const config = {
reactStrictMode: true,,
}
export default config
`
)
const { stderr } = await nextBuild(appDir, undefined, {
stderr: true,
})
await fs.remove(nextConfigMJS)

expect(stderr).toContain(
'error - Failed to load next.config.mjs, see more info here https://nextjs.org/docs/messages/next-config-error'
)
expect(stderr).toContain('SyntaxError')
})
Expand Down

0 comments on commit 6f3e947

Please sign in to comment.