mirror of
https://github.com/hanzoai/next.js.git
synced 2026-09-24 17:00:51 +00:00
### What?
Adds build-time validation to require explicit `default.js` files for
all parallel route slots (except the implicit "children" slot). This
validation is implemented in both Webpack and Turbopack bundlers.
### Why?
Parallel routes without `default.js` files currently cause silent 404
errors when users navigate to those routes. This creates confusion and
hard-to-debug issues because the routes appear to be configured
correctly but fail at runtime without any indication of what went wrong.
By making this validation explicit at build time, developers get
immediate feedback about missing required files with clear error
messages and documentation links, catching configuration mistakes before
deployment.
### How?
**Rust/Turbopack** (`crates/next-core/src/app_structure.rs`): Added
`MissingDefaultParallelRouteIssue` that emits a build error when a
parallel route slot is missing its `default.js` file. The validation is
skipped for the "children" slot since it's implicit and doesn't require
a default file.
**Webpack**
(`packages/next/src/build/webpack/loaders/next-app-loader/index.ts`):
Added validation that throws `MissingDefaultParallelRouteError` when
`default.js` cannot be resolved. The "children" slot falls back to the
existing `PARALLEL_ROUTE_DEFAULT_PATH` behavior for backward
compatibility.
**Error Class**
(`packages/next/src/shared/lib/errors/missing-default-parallel-route-error.ts`):
New error type with helpful messaging that includes the slot path,
explanation of the requirement, and a link to documentation.
**Migration Path**: Users who want the previous 404 behavior can
explicitly create a `default.js` that calls `notFound()`, or return
`null` for empty slots:
```tsx
import { notFound } from 'next/navigation'
export default function Default() {
notFound()
}
```
Users can also run the following Deno script to generate the default
files for them:
https://gist.github.com/wyattjoh/ba7263ecb637ef399d3e3e4db63ffbd6
**Breaking Change**: This is a breaking change timed for Next.js 16
beta. Builds will now fail if parallel route slots are missing required
`default.js` files.
118 lines
4.3 KiB
TypeScript
118 lines
4.3 KiB
TypeScript
import { nextTestSetup } from 'e2e-utils'
|
|
|
|
describe('parallel-route-not-found', () => {
|
|
const { next, isNextDev } = nextTestSetup({
|
|
files: __dirname,
|
|
})
|
|
|
|
// TODO: adjust the test to work with the new error
|
|
it.skip('should handle a layout that attempts to render a missing parallel route', async () => {
|
|
const browser = await next.browser('/no-bar-slot')
|
|
const logs = await browser.log()
|
|
expect(await browser.elementByCss('body').text()).toContain(
|
|
'This page could not be found'
|
|
)
|
|
const warnings = logs.filter((log) => log.source === 'warning')
|
|
if (isNextDev) {
|
|
expect(warnings.length).toBe(1)
|
|
expect(warnings[0].message).toContain(
|
|
'No default component was found for a parallel route rendered on this page'
|
|
)
|
|
expect(warnings[0].message).toContain('Missing slots: @bar')
|
|
} else {
|
|
expect(warnings.length).toBe(0)
|
|
}
|
|
})
|
|
|
|
// TODO: adjust the test to work with the new error
|
|
it.skip('should handle multiple missing parallel routes', async () => {
|
|
const browser = await next.browser('/both-slots-missing')
|
|
const logs = await browser.log()
|
|
|
|
expect(await browser.elementByCss('body').text()).toContain(
|
|
'This page could not be found'
|
|
)
|
|
|
|
const warnings = logs.filter((log) => log.source === 'warning')
|
|
if (isNextDev) {
|
|
expect(warnings.length).toBe(1)
|
|
expect(warnings[0].message).toContain(
|
|
'No default component was found for a parallel route rendered on this page'
|
|
)
|
|
expect(warnings[0].message).toContain('Missing slots: @bar, @foo')
|
|
} else {
|
|
expect(warnings.length).toBe(0)
|
|
}
|
|
})
|
|
|
|
it('should not include any parallel route warnings for a deliberate notFound()', async () => {
|
|
const browser = await next.browser('/has-both-slots/not-found-error')
|
|
const logs = await browser.log()
|
|
|
|
expect(await browser.elementByCss('body').text()).toContain(
|
|
'This page could not be found'
|
|
)
|
|
|
|
const warnings = logs.filter((log) => log.source === 'warning')
|
|
expect(warnings.length).toBe(0)
|
|
})
|
|
|
|
it('should render the page & slots if all parallel routes are found', async () => {
|
|
const browser = await next.browser('/has-both-slots')
|
|
const logs = await browser.log()
|
|
|
|
expect(await browser.elementByCss('body').text()).toContain(
|
|
'Has Both Slots'
|
|
)
|
|
expect(await browser.elementByCss('body').text()).toContain('@foo slot')
|
|
expect(await browser.elementByCss('body').text()).toContain('@bar slot')
|
|
|
|
const warnings = logs.filter((log) => log.source === 'warning')
|
|
expect(warnings.length).toBe(0)
|
|
})
|
|
|
|
it('should handle `notFound()` in generateMetadata on a page that also renders a parallel route', async () => {
|
|
const browser = await next.browser('/not-found-metadata/page-error')
|
|
|
|
// The page's `generateMetadata` function threw a `notFound()` error,
|
|
// so we should see the not found page.
|
|
expect(await browser.elementByCss('body').text()).toContain(
|
|
'Custom Not Found!'
|
|
)
|
|
})
|
|
|
|
it('should handle `notFound()` in a slot', async () => {
|
|
const browser = await next.browser('/not-found-metadata/slot-error')
|
|
|
|
// The page's `generateMetadata` function threw a `notFound()` error,
|
|
// so we should see the not found page.
|
|
expect(await browser.elementByCss('body').text()).toContain(
|
|
'Custom Not Found!'
|
|
)
|
|
})
|
|
|
|
// TODO-APP: This test should probably work. But we only provide a not-found boundary for the children slot.
|
|
// This means that if a parallel route throws a notFound() in generateMetadata, it won't be properly handled.
|
|
it.skip('should handle `notFound()` in a slot with no `children` slot', async () => {
|
|
const browser = await next.browser('/not-found-metadata/no-page')
|
|
|
|
// The page's `generateMetadata` function threw a `notFound()` error,
|
|
// so we should see the not found page.
|
|
expect(await browser.elementByCss('body').text()).toContain(
|
|
'Custom Not Found!'
|
|
)
|
|
})
|
|
|
|
if (isNextDev) {
|
|
it('should not log any warnings for a regular not found page', async () => {
|
|
const browser = await next.browser('/this-page-doesnt-exist')
|
|
const logs = await browser.log()
|
|
expect(await browser.elementByCss('body').text()).toContain(
|
|
'This page could not be found'
|
|
)
|
|
const warnings = logs.filter((log) => log.source === 'warning')
|
|
expect(warnings.length).toBe(0)
|
|
})
|
|
}
|
|
})
|