Files
next.js/test/development/acceptance/server-component-compiler-errors-in-pages.test.ts
66478522a8 Improve RSC error messages (#90702)
Improve the RSC error messages to be more comprehensive.

x-ref:
https://github.com/vercel/next.js/pull/89688#discussion_r2865847895

---------

Co-authored-by: Claude Haiku 4.5 <[email protected]>
2026-03-17 14:37:26 +01:00

445 lines
15 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* eslint-env jest */
import { nextTestSetup } from 'e2e-utils'
import { createSandbox } from 'development-sandbox'
import { outdent } from 'outdent'
const isRspack = process.env.NEXT_RSPACK !== undefined
const initialFiles = new Map([
['app/_.js', ''], // app dir need to exists, otherwise the SWC RSC checks will not run
[
'pages/index.js',
outdent`
import Comp from '../components/Comp'
export default function Page() { return <Comp /> }
`,
],
[
'components/Comp.js',
outdent`
export default function Comp() {
return <p>Hello world</p>
}
`,
],
])
describe('Error Overlay for server components compiler errors in pages', () => {
const { next } = nextTestSetup({
files: {},
skipStart: true,
})
test("importing 'next/headers' in pages", async () => {
await using sandbox = await createSandbox(next, initialFiles)
const { session } = sandbox
await session.patch(
'components/Comp.js',
outdent`
import { cookies } from 'next/headers'
export default function Page() {
return <p>hello world</p>
}
`
)
await session.waitForRedbox()
await expect(session.getRedboxSource()).resolves.toMatch(
/This API is only available in Server Components/
)
if (process.env.IS_TURBOPACK_TEST) {
expect(next.normalizeTestDirContent(await session.getRedboxSource()))
.toMatchInlineSnapshot(`
"./components/Comp.js (1:1)
You're importing a module that depends on "next/headers". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.
Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
> 1 | import { cookies } from 'next/headers'
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 |
3 | export default function Page() {
4 | return <p>hello world</p>
Ecmascript file had an error
Import traces:
Browser:
./components/Comp.js
./pages/index.js
SSR:
./components/Comp.js
./pages/index.js"
`)
} else if (isRspack) {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'----'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
╰─▶ × Error: x You're importing a module that depends on "next/headers". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.
│ | Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
│ |
│
│ ,-[1:1]
│ 1 | import { cookies } from 'next/headers'
│ : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
│ 2 |
│ 3 | export default function Page() {
│ 4 | return <p>hello world</p>
│ \`----"
`)
} else {
expect(next.normalizeTestDirContent(await session.getRedboxSource()))
.toMatchInlineSnapshot(`
"./components/Comp.js
Error: x You're importing a module that depends on "next/headers". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.
| Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
|
,-[1:1]
1 | import { cookies } from 'next/headers'
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 |
3 | export default function Page() {
4 | return <p>hello world</p>
\`----
Import trace for requested module:
./components/Comp.js
./pages/index.js"
`)
}
})
test("importing 'server-only' in pages", async () => {
await using sandbox = await createSandbox(next, initialFiles)
const { session } = sandbox
await next.patchFile(
'components/Comp.js',
outdent`
import 'server-only'
export default function Page() {
return 'hello world'
}
`
)
await session.waitForRedbox()
await expect(session.getRedboxSource()).resolves.toMatch(
/This API is only available in Server Components/
)
if (process.env.IS_TURBOPACK_TEST) {
expect(next.normalizeTestDirContent(await session.getRedboxSource()))
.toMatchInlineSnapshot(`
"./components/Comp.js (1:1)
You're importing a module that depends on "server-only". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.
Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
> 1 | import 'server-only'
| ^^^^^^^^^^^^^^^^^^^^
2 |
3 | export default function Page() {
4 | return 'hello world'
Ecmascript file had an error
Import traces:
Browser:
./components/Comp.js
./pages/index.js
SSR:
./components/Comp.js
./pages/index.js"
`)
} else if (isRspack) {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'----'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
╰─▶ × Error: x You're importing a module that depends on "server-only". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.
│ | Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
│ |
│
│ ,-[1:1]
│ 1 | import 'server-only'
│ : ^^^^^^^^^^^^^^^^^^^^
│ 2 |
│ 3 | export default function Page() {
│ 4 | return 'hello world'
│ \`----"
`)
} else {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'Import trace for requested module:'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
Error: x You're importing a module that depends on "server-only". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.
| Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
|
,-[1:1]
1 | import 'server-only'
: ^^^^^^^^^^^^^^^^^^^^
2 |
3 | export default function Page() {
4 | return 'hello world'
\`----
Import trace for requested module:"
`)
}
})
test("importing after from 'next/server' in pages", async () => {
await using sandbox = await createSandbox(next, initialFiles)
const { session } = sandbox
await next.patchFile(
'components/Comp.js',
outdent`
import { after } from 'next/server'
export default function Page() {
return 'hello world'
}
`
)
await session.waitForRedbox()
await expect(session.getRedboxSource()).resolves.toMatch(
/This API is only available in Server Components/
)
if (process.env.IS_TURBOPACK_TEST) {
expect(next.normalizeTestDirContent(await session.getRedboxSource()))
.toMatchInlineSnapshot(`
"./components/Comp.js (1:10)
You're importing a module that depends on "after". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.
Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
> 1 | import { after } from 'next/server'
| ^^^^^
2 |
3 | export default function Page() {
4 | return 'hello world'
Ecmascript file had an error
Import traces:
Browser:
./components/Comp.js
./pages/index.js
SSR:
./components/Comp.js
./pages/index.js"
`)
} else if (isRspack) {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'----'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
╰─▶ × Error: x You're importing a module that depends on "after". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.
│ | Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
│ |
│
│ ,-[1:1]
│ 1 | import { after } from 'next/server'
│ : ^^^^^
│ 2 |
│ 3 | export default function Page() {
│ 4 | return 'hello world'
│ \`----"
`)
} else {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'Import trace for requested module:'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
Error: x You're importing a module that depends on "after". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.
| Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
|
,-[1:1]
1 | import { after } from 'next/server'
: ^^^^^
2 |
3 | export default function Page() {
4 | return 'hello world'
\`----
Import trace for requested module:"
`)
}
})
test("importing 'next/root-params' in pages", async () => {
const files = new Map([
...initialFiles,
[
'components/Comp.js',
outdent`
import { foo } from 'next/root-params'
export default function Page() {
return 'hello world'
}
`,
],
[
// the import is guarded behind an experimental flag
'next.config.js',
outdent`
module.exports = { experimental: { rootParams: true } }
`,
],
])
await using sandbox = await createSandbox(next, files)
const { session } = sandbox
await session.waitForRedbox()
await expect(session.getRedboxSource()).resolves.toMatch(
/This API is only available in Server Components/
)
if (process.env.IS_TURBOPACK_TEST) {
expect(next.normalizeTestDirContent(await session.getRedboxSource()))
.toMatchInlineSnapshot(`
"./components/Comp.js (1:1)
You're importing a module that depends on "next/root-params". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.
Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
> 1 | import { foo } from 'next/root-params'
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 |
3 | export default function Page() {
4 | return 'hello world'
Ecmascript file had an error
Import traces:
Browser:
./components/Comp.js
./pages/index.js
SSR:
./components/Comp.js
./pages/index.js"
`)
} else if (isRspack) {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'----'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
╰─▶ × Error: x You're importing a module that depends on "next/root-params". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.
│ | Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
│ |
│
│ ,-[1:1]
│ 1 | import { foo } from 'next/root-params'
│ : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
│ 2 |
│ 3 | export default function Page() {
│ 4 | return 'hello world'
│ \`----"
`)
} else {
expect(
takeUpToString(
next.normalizeTestDirContent(await session.getRedboxSource()),
'Import trace for requested module:'
)
).toMatchInlineSnapshot(`
"./components/Comp.js
Error: x You're importing a module that depends on "next/root-params". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.
| Learn more: https://nextjs.org/docs/app/building-your-application/rendering/server-components
|
,-[1:1]
1 | import { foo } from 'next/root-params'
: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
2 |
3 | export default function Page() {
4 | return 'hello world'
\`----"
`)
}
})
describe("importing 'next/cache' APIs in pages", () => {
test.each([
'revalidatePath',
'revalidateTag',
'cacheLife',
'cacheTag',
'revalidatePath',
'revalidateTag',
])('%s is not allowed', async (api) => {
await using sandbox = await createSandbox(next, initialFiles)
const { session } = sandbox
await next.patchFile(
'components/Comp.js',
outdent`
import { ${api} } from 'next/cache'
export default function Page() {
return 'hello world'
}
`
)
await session.waitForRedbox()
await expect(session.getRedboxSource()).resolves.toMatch(
`You're importing a module that depends on "${api}". This API is only available in Server Components in the App Router, but you are using it in the Pages Router.`
)
})
test.each([
'unstable_cache', // useless in client, but doesn't technically error
'unstable_noStore', // no-op in client, but allowed for legacy reasons
])('%s is allowed', async (api) => {
await using sandbox = await createSandbox(next, initialFiles)
const { session } = sandbox
await next.patchFile(
'components/Comp.js',
outdent`
import { ${api} } from 'next/cache'
export default function Page() {
return 'hello world'
}
`
)
await session.waitForNoRedbox()
})
})
})
const takeUpToString = (text: string, str: string): string =>
text.includes(str) ? text.slice(0, text.indexOf(str) + str.length) : text