Skip to content

Commit

Permalink
dx: add pagePath info to missing component error (#66916)
Browse files Browse the repository at this point in the history
### What 

Show the full `pagePath` information for missing default component app
router convention files.

Move runtime error tests from `rsc-build-errors.test.ts` to the new test
`test/development/acceptance-app/undefined-default-export.test.ts`

### Why

Previously we only log `segment` which could be `[slug]` that is not
enough useful for users to locate the bad defined component
  • Loading branch information
huozhi authored and pull[bot] committed Jul 10, 2024
1 parent 51ec3f6 commit 2478adb
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 47 deletions.
22 changes: 10 additions & 12 deletions packages/next/src/server/app-render/create-component-tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ export function createComponentTree(props: {
)
}

function errorMissingDefaultExport(pagePath: string, convention: string) {
throw new Error(
`The default export is not a React Component in "${pagePath}/${convention}"`
)
}

async function createComponentTreeInternal({
createSegmentPath,
loaderTree: tree,
Expand Down Expand Up @@ -311,30 +317,22 @@ async function createComponentTreeInternal({
(isPage || typeof Component !== 'undefined') &&
!isValidElementType(Component)
) {
throw new Error(
`The default export is not a React Component in page: "${pagePath}"`
)
errorMissingDefaultExport(pagePath, 'page')
}

if (
typeof ErrorComponent !== 'undefined' &&
!isValidElementType(ErrorComponent)
) {
throw new Error(
`The default export of error is not a React Component in page: ${segment}`
)
errorMissingDefaultExport(pagePath, 'error')
}

if (typeof Loading !== 'undefined' && !isValidElementType(Loading)) {
throw new Error(
`The default export of loading is not a React Component in ${segment}`
)
errorMissingDefaultExport(pagePath, 'loading')
}

if (typeof NotFound !== 'undefined' && !isValidElementType(NotFound)) {
throw new Error(
`The default export of notFound is not a React Component in ${segment}`
)
errorMissingDefaultExport(pagePath, 'not-found')
}
}

Expand Down
35 changes: 0 additions & 35 deletions test/development/acceptance-app/rsc-build-errors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,41 +124,6 @@ describe('Error overlay - RSC build errors', () => {
await cleanup()
})

it('should error when page component export is not valid', async () => {
const { session, cleanup } = await sandbox(
next,
undefined,
'/server-with-errors/page-export'
)

await next.patchFile(
'app/server-with-errors/page-export/page.js',
'export const a = 123'
)

await session.assertHasRedbox()
expect(await session.getRedboxDescription()).toInclude(
'The default export is not a React Component in page: "/server-with-errors/page-export"'
)

await cleanup()
})

it('should error when page component export is not valid on initial load', async () => {
const { session, cleanup } = await sandbox(
next,
undefined,
'/server-with-errors/page-export-initial-error'
)

await session.assertHasRedbox()
expect(await session.getRedboxDescription()).toInclude(
'The default export is not a React Component in page: "/server-with-errors/page-export-initial-error"'
)

await cleanup()
})

it('should throw an error when "use client" is on the top level but after other expressions', async () => {
const { session, cleanup } = await sandbox(
next,
Expand Down
90 changes: 90 additions & 0 deletions test/development/acceptance-app/undefined-default-export.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import path from 'path'
import { FileRef, nextTestSetup } from 'e2e-utils'
import { sandbox } from 'development-sandbox'

describe('Undefined default export', () => {
const { next } = nextTestSetup({
files: new FileRef(path.join(__dirname, 'fixtures', 'default-template')),
})

it('should error if page component does not have default export', async () => {
const { session, cleanup } = await sandbox(
next,
new Map([
['app/(group)/specific-path/server/page.js', 'export const a = 123'],
]),
'/specific-path/server'
)

await session.assertHasRedbox()
expect(await session.getRedboxDescription()).toInclude(
'The default export is not a React Component in "/specific-path/server/page"'
)

await cleanup()
})

it('should error if not-found component does not have default export when trigger not-found boundary', async () => {
const { session, cleanup } = await sandbox(
next,
new Map([
[
'app/will-not-found/page.js',
`
import { notFound } from 'next/navigation'
export default function Page() { notFound() }
`,
],
['app/will-not-found/not-found.js', 'export const a = 123'],
]),
'/will-not-found'
)

await session.assertHasRedbox()
expect(await session.getRedboxDescription()).toInclude(
'The default export is not a React Component in "/will-not-found/not-found"'
)

await cleanup()
})

it('should error when page component export is not valid', async () => {
const { session, cleanup } = await sandbox(
next,
undefined,
'/server-with-errors/page-export'
)

await next.patchFile(
'app/server-with-errors/page-export/page.js',
'export const a = 123'
)

await session.assertHasRedbox()
expect(await session.getRedboxDescription()).toInclude(
'The default export is not a React Component in "/server-with-errors/page-export/page"'
)

await cleanup()
})

it('should error when page component export is not valid on initial load', async () => {
const { session, cleanup } = await sandbox(
next,
new Map([
[
'app/server-with-errors/page-export-initial-error/page.js',
'export const a = 123',
],
]),
'/server-with-errors/page-export-initial-error'
)

await session.assertHasRedbox()
expect(await session.getRedboxDescription()).toInclude(
'The default export is not a React Component in "/server-with-errors/page-export-initial-error/page"'
)

await cleanup()
})
})

0 comments on commit 2478adb

Please sign in to comment.