Skip to content

Commit 44afc37

Browse files
authored
Refine error messages (#40661)
As per @sebmarkbage's suggestion, this PR refines the error message to be more readable and friendly: ``` error - ./components/qux.js You're importing a component that needs useEffect. It only works in a Client Component but none of its parents are marked with "client", so they're Server Components by default. ,---- 5 | import { useEffect } from 'react' : ^^^^^^^^^ `---- Maybe one of these should be marked as a "client" entry: ./components/qux.js ./components/baz.js ./components/bar.js ./app/dashboard/index/page.js ``` It's more ideal to put error codes inside the SWC transform and format it in the Next.js logging layer. A future improvement is to tweak the message a bit more for these specific cases: - [ ] The error already happens inside an entry point (page or layout), so itself should have "client" added (or removed), not it parents. - [ ] When importing `"server-only"` inside a client component, we can directly hint to the user which module in the import chain is marked with `"client"`. ## Bug - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Errors have a helpful link attached, see `contributing.md` ## Feature - [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR. - [ ] Related issues linked using `fixes #number` - [ ] Integration tests added - [ ] Documentation added - [ ] Telemetry added. In case of a feature if it's used or not. - [ ] Errors have a helpful link attached, see `contributing.md` ## Documentation / Examples - [ ] 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/examples/adding-examples.md)
1 parent 6df8770 commit 44afc37

File tree

8 files changed

+73
-67
lines changed

8 files changed

+73
-67
lines changed

packages/next-swc/crates/core/src/react_server_components.rs

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -227,11 +227,7 @@ impl<C: Comments> ReactServerComponents<C> {
227227
handler
228228
.struct_span_err(
229229
import.source.1,
230-
format!(
231-
"Disallowed import of `{}` in the Server Components compilation.",
232-
source
233-
)
234-
.as_str(),
230+
format!("NEXT_RSC_ERR_SERVER_IMPORT: {}", source).as_str(),
235231
)
236232
.emit()
237233
})
@@ -243,12 +239,7 @@ impl<C: Comments> ReactServerComponents<C> {
243239
handler
244240
.struct_span_err(
245241
specifier.1,
246-
format!(
247-
"Disallowed React API `{}` in the Server Components \
248-
compilation.",
249-
&specifier.0
250-
)
251-
.as_str(),
242+
format!("NEXT_RSC_ERR_REACT_API: {}", &specifier.0).as_str(),
252243
)
253244
.emit()
254245
})
@@ -262,12 +253,7 @@ impl<C: Comments> ReactServerComponents<C> {
262253
handler
263254
.struct_span_err(
264255
specifier.1,
265-
format!(
266-
"Disallowed ReactDOM API `{}` in the Server Components \
267-
compilation.",
268-
&specifier.0
269-
)
270-
.as_str(),
256+
format!("NEXT_RSC_ERR_REACT_API: {}", &specifier.0).as_str(),
271257
)
272258
.emit()
273259
})
@@ -285,11 +271,7 @@ impl<C: Comments> ReactServerComponents<C> {
285271
handler
286272
.struct_span_err(
287273
import.source.1,
288-
format!(
289-
"Disallowed import of `{}` in the Client Components compilation.",
290-
source
291-
)
292-
.as_str(),
274+
format!("NEXT_RSC_ERR_CLIENT_IMPORT: {}", source).as_str(),
293275
)
294276
.emit()
295277
})

packages/next-swc/crates/core/tests/errors/react-server-components/client-graph/server-only/output.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
x Disallowed import of `server-only` in the Client Components compilation.
2+
x NEXT_RSC_ERR_CLIENT_IMPORT: server-only
33
,-[input.js:9:1]
44
9 | import "server-only"
55
: ^^^^^^^^^^^^^^^^^^^^

packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/client-only/output.stderr

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
x Disallowed import of `client-only` in the Server Components compilation.
2+
x NEXT_RSC_ERR_SERVER_IMPORT: client-only
33
,-[input.js:9:1]
44
9 | import "client-only"
55
: ^^^^^^^^^^^^^^^^^^^^

packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/react-api/output.stderr

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,77 @@
11

2-
x Disallowed React API `useState` in the Server Components compilation.
2+
x NEXT_RSC_ERR_REACT_API: useState
33
,-[input.js:1:1]
44
1 | import { useState } from 'react'
55
: ^^^^^^^^
66
`----
77

8-
x Disallowed React API `createContext` in the Server Components compilation.
8+
x NEXT_RSC_ERR_REACT_API: createContext
99
,-[input.js:3:1]
1010
3 | import { createContext } from 'react'
1111
: ^^^^^^^^^^^^^
1212
`----
1313

14-
x Disallowed React API `useEffect` in the Server Components compilation.
14+
x NEXT_RSC_ERR_REACT_API: useEffect
1515
,-[input.js:5:1]
1616
5 | import { useEffect, useImperativeHandle } from 'react'
1717
: ^^^^^^^^^
1818
`----
1919

20-
x Disallowed React API `useImperativeHandle` in the Server Components compilation.
20+
x NEXT_RSC_ERR_REACT_API: useImperativeHandle
2121
,-[input.js:5:1]
2222
5 | import { useEffect, useImperativeHandle } from 'react'
2323
: ^^^^^^^^^^^^^^^^^^^
2424
`----
2525

26-
x Disallowed React API `Component` in the Server Components compilation.
26+
x NEXT_RSC_ERR_REACT_API: Component
2727
,-[input.js:8:5]
2828
8 | Component,
2929
: ^^^^^^^^^
3030
`----
3131

32-
x Disallowed React API `createFactory` in the Server Components compilation.
32+
x NEXT_RSC_ERR_REACT_API: createFactory
3333
,-[input.js:9:5]
3434
9 | createFactory,
3535
: ^^^^^^^^^^^^^
3636
`----
3737

38-
x Disallowed React API `PureComponent` in the Server Components compilation.
38+
x NEXT_RSC_ERR_REACT_API: PureComponent
3939
,-[input.js:10:5]
4040
10 | PureComponent,
4141
: ^^^^^^^^^^^^^
4242
`----
4343

44-
x Disallowed React API `useDeferredValue` in the Server Components compilation.
44+
x NEXT_RSC_ERR_REACT_API: useDeferredValue
4545
,-[input.js:11:3]
4646
11 | useDeferredValue,
4747
: ^^^^^^^^^^^^^^^^
4848
`----
4949

50-
x Disallowed React API `useInsertionEffect` in the Server Components compilation.
50+
x NEXT_RSC_ERR_REACT_API: useInsertionEffect
5151
,-[input.js:12:5]
5252
12 | useInsertionEffect,
5353
: ^^^^^^^^^^^^^^^^^^
5454
`----
5555

56-
x Disallowed React API `useLayoutEffect` in the Server Components compilation.
56+
x NEXT_RSC_ERR_REACT_API: useLayoutEffect
5757
,-[input.js:13:5]
5858
13 | useLayoutEffect,
5959
: ^^^^^^^^^^^^^^^
6060
`----
6161

62-
x Disallowed React API `useReducer` in the Server Components compilation.
62+
x NEXT_RSC_ERR_REACT_API: useReducer
6363
,-[input.js:14:5]
6464
14 | useReducer,
6565
: ^^^^^^^^^^
6666
`----
6767

68-
x Disallowed React API `useRef` in the Server Components compilation.
68+
x NEXT_RSC_ERR_REACT_API: useRef
6969
,-[input.js:15:5]
7070
15 | useRef,
7171
: ^^^^^^
7272
`----
7373

74-
x Disallowed React API `useSyncExternalStore` in the Server Components compilation.
74+
x NEXT_RSC_ERR_REACT_API: useSyncExternalStore
7575
,-[input.js:16:5]
7676
16 | useSyncExternalStore
7777
: ^^^^^^^^^^^^^^^^^^^^

packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/react-dom-api/output.stderr

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11

2-
x Disallowed ReactDOM API `findDOMNode` in the Server Components compilation.
2+
x NEXT_RSC_ERR_REACT_API: findDOMNode
33
,-[input.js:2:5]
44
2 | findDOMNode,
55
: ^^^^^^^^^^^
66
`----
77

8-
x Disallowed ReactDOM API `flushSync` in the Server Components compilation.
8+
x NEXT_RSC_ERR_REACT_API: flushSync
99
,-[input.js:3:3]
1010
3 | flushSync,
1111
: ^^^^^^^^^
1212
`----
1313

14-
x Disallowed ReactDOM API `unstable_batchedUpdates` in the Server Components compilation.
14+
x NEXT_RSC_ERR_REACT_API: unstable_batchedUpdates
1515
,-[input.js:4:3]
1616
4 | unstable_batchedUpdates,
1717
: ^^^^^^^^^^^^^^^^^^^^^^^

packages/next-swc/crates/core/tests/errors/react-server-components/server-graph/react-dom-server-client/output.stderr

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

2-
x Disallowed import of `react-dom/server` in the Server Components compilation.
2+
x NEXT_RSC_ERR_SERVER_IMPORT: react-dom/server
33
,-[input.js:9:1]
44
9 | import "react-dom/server"
55
: ^^^^^^^^^^^^^^^^^^^^^^^^^
66
`----
77

8-
x Disallowed import of `react-dom/client` in the Server Components compilation.
8+
x NEXT_RSC_ERR_SERVER_IMPORT: react-dom/client
99
,-[input.js:11:1]
1010
11 | import "react-dom/client"
1111
: ^^^^^^^^^^^^^^^^^^^^^^^^^

packages/next/build/webpack-config.ts

Lines changed: 15 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1474,24 +1474,22 @@ export default async function getBaseWebpackConfig(
14741474
} as any,
14751475
]
14761476
: []),
1477-
...(hasServerComponents
1478-
? isNodeServer || isEdgeServer
1479-
? [
1480-
// RSC server compilation loaders
1481-
{
1482-
test: codeCondition.test,
1483-
include: [
1484-
dir,
1485-
// To let the internal client components passing through flight loader
1486-
/next[\\/]dist/,
1487-
],
1488-
issuerLayer: WEBPACK_LAYERS.server,
1489-
use: {
1490-
loader: 'next-flight-loader',
1491-
},
1477+
...(hasServerComponents && (isNodeServer || isEdgeServer)
1478+
? [
1479+
// RSC server compilation loaders
1480+
{
1481+
test: codeCondition.test,
1482+
include: [
1483+
dir,
1484+
// To let the internal client components passing through flight loader
1485+
/next[\\/]dist/,
1486+
],
1487+
issuerLayer: WEBPACK_LAYERS.server,
1488+
use: {
1489+
loader: 'next-flight-loader',
14921490
},
1493-
]
1494-
: []
1491+
},
1492+
]
14951493
: []),
14961494
...(hasServerComponents && isEdgeServer
14971495
? [

packages/next/client/dev/error-overlay/format-webpack-messages.js

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ function isLikelyASyntaxError(message) {
3737
let hadMissingSassError = false
3838

3939
// Cleans up webpack error messages.
40-
function formatMessage(message, verbose) {
40+
function formatMessage(message, verbose, importTraceNote) {
4141
// TODO: Replace this once webpack 5 is stable
4242
if (typeof message === 'object' && message.message) {
4343
const filteredModuleTrace =
@@ -61,7 +61,7 @@ function formatMessage(message, verbose) {
6161
body +
6262
(message.details && verbose ? '\n' + message.details : '') +
6363
(filteredModuleTrace && filteredModuleTrace.length && verbose
64-
? '\n\nImport trace for requested module:' +
64+
? (importTraceNote || '\n\nImport trace for requested module:') +
6565
filteredModuleTrace.map((trace) => `\n${trace.moduleName}`).join('')
6666
: '') +
6767
(message.stack && verbose ? '\n' + message.stack : '')
@@ -171,18 +171,44 @@ function formatMessage(message, verbose) {
171171

172172
function formatWebpackMessages(json, verbose) {
173173
const formattedErrors = json.errors.map(function (message) {
174+
let importTraceNote
175+
174176
// TODO: Shall we use invisible characters in the original error
175177
// message as meta information?
176-
if (
177-
message &&
178-
message.message &&
179-
/ (Client|Server) Components compilation\./.test(message.message)
180-
) {
178+
if (message && message.message && /NEXT_RSC_ERR_/.test(message.message)) {
181179
// Comes from the "React Server Components" transform in SWC, always
182180
// attach the module trace.
181+
const NEXT_RSC_ERR_REACT_API = /.+NEXT_RSC_ERR_REACT_API: (.*?)\n/s
182+
const NEXT_RSC_ERR_SERVER_IMPORT =
183+
/.+NEXT_RSC_ERR_SERVER_IMPORT: (.*?)\n/s
184+
const NEXT_RSC_ERR_CLIENT_IMPORT =
185+
/.+NEXT_RSC_ERR_CLIENT_IMPORT: (.*?)\n/s
186+
187+
if (NEXT_RSC_ERR_REACT_API.test(message.message)) {
188+
message.message = message.message.replace(
189+
NEXT_RSC_ERR_REACT_API,
190+
`\n\nYou're importing a component that needs $1. It only works in a Client Component but none of its parents are marked with "client", so they're Server Components by default.\n\n`
191+
)
192+
importTraceNote =
193+
'\n\nMaybe one of these should be marked as a "client" entry:\n'
194+
} else if (NEXT_RSC_ERR_SERVER_IMPORT.test(message.message)) {
195+
message.message = message.message.replace(
196+
NEXT_RSC_ERR_SERVER_IMPORT,
197+
`\n\nYou're importing a component that imports $1. It only works in a Client Component but none of its parents are marked with "client", so they're Server Components by default.\n\n`
198+
)
199+
importTraceNote =
200+
'\n\nMaybe one of these should be marked as a "client" entry:\n'
201+
} else if (NEXT_RSC_ERR_CLIENT_IMPORT.test(message.message)) {
202+
message.message = message.message.replace(
203+
NEXT_RSC_ERR_CLIENT_IMPORT,
204+
`\n\nYou're importing a component that needs $1. That only works in a Server Component but one of its parents is marked with "client", so it's a Client Component.\n\n`
205+
)
206+
importTraceNote = '\n\nOne of these is marked as a "client" entry:\n'
207+
}
208+
183209
verbose = true
184210
}
185-
return formatMessage(message, verbose)
211+
return formatMessage(message, verbose, importTraceNote)
186212
})
187213
const formattedWarnings = json.warnings.map(function (message) {
188214
return formatMessage(message, verbose)

0 commit comments

Comments
 (0)