Commit graph

4 commits

Author SHA1 Message Date
Zack Tanner
31a0edbd1b
parallel routes: fix catch-all slots being treated as optional catch-all (#61174)
### What
Catch-all parallel slots were being incorrectly matched to the root of their segment. For example, `@foo/[...catchAll]/page` as a parallel route on `/page.tsx` should not match on `/`, but it should match on `/foo`, `/bar`, ...etc

### Why
The catch-all route normalization logic doesn't treat optional catch-all routes differently from catch-all routes. The assumption was if any catch-all route was found, that it should match the path that shared its prefix.

### How
This updates the normalization logic to handle optional-catchall as it was in the original implementation. For regular catch-all, we ensure that the catch-all base path (for `/[...slug]` that'd be `/`) isn't identical to the path we'd match it to.

Fixes #60613
Closes NEXT-2243
2024-01-30 15:28:47 -08:00
Zack Tanner
d6c754f332
parallel routes: fix client reference manifest grouping for catch-all segments (#60482)
### What?
When using catch-all routes in conjunction with parallel routes, and
when importing a client component (`"use client"`), the build would fail
with the following error:

> Could not find the module "PathToClientComponent" in the React Client
Manifest. This is probably a bug in the React Server Components bundler.

### Why?
`flight-manifest-plugin` generates manifests for each page entry. The
`clientModules` portion of this manifest is used by React to load the
appropriate client module. When React attempts to render a component
tree and detects a module that it cannot find, it will throw this error.
To illustrate why it isn't in the tree, consider the following example:

```
app
  page.tsx
  layout.tsx
  @slot
    [...catchAll]
      page.tsx
```

```tsx
// app/layout.tsx
export default function Layout({children, slot}) {
  return <>{children} {slot}</>
}
```
```tsx
// app/@slot/[...catchAll]/page.tsx
import Link from 'next/link'
export default function Page() {
  return <Link href="/">Test</Link>
}
```

When visiting `/`, we'd expect both the catch-all `@slot` and the root
page to render. At build time, we'll generate a client reference
manifest for `/` and `/[...catchAll]` since both are page components.
However, the `@slot` imports a client component. When we attempt to load
the client reference manifest for `/`, it will ignore the catch-all
slot's manifest, resulting in the error.

### How?
The `entryNameToGroupName` function seems to already exist to handle
this scenario for other cases. For example,
`app/(group)/@named/foo/page` needs to know about any manifests
associated with `app/foo`. This updates the code to apply similar
handling to catchAll segments. When applying this change to the example
mentioned earlier, it will properly merge the manifests for both
`app/@slot/[...catchAll]/page.tsx` and `app/page.tsx` because both will
be part of the `/` group.

Closes NEXT-1908
Fixes #59747
Fixes #59510
2024-01-10 14:14:19 -08:00
Zack Tanner
90f95399dd
fix catch-all route normalization for default parallel routes (#60240)
### What?
When relying on a `default` route as a fallback, greedier catch-all
segments in the application hierarchy would take precedence, causing
unexpected errors/matching behavior.

### Why?
When performing parallel route catch-all normalization, we push
potential catch-all matches to paths without considering that a path
might instead be matched by a `default` page. Because of this, the
catch-all take precedence and the app will not try and load the default.

For example, given this structure:

```
{
  "/": ["/page"],
  "/[[...catchAll]]": ["/[[...catchAll]]/page"],
  "/nested/[foo]/[bar]": ["/nested/[foo]/[bar]/@slot/page"],
  "/nested/[foo]/[bar]/[baz]": ["/nested/[foo]/[bar]/@slot/[baz]/page"],
}
```

(Where there's a `/nested/[foo]/[bar]/default.tsx`)

The route normalization logic would produce:

```
{
  "/": ["/page"],
  "/[[...catchAll]]": ["/[[...catchAll]]/page"],
  "/nested/[foo]/[bar]": [
    "/nested/[foo]/[bar]/@slot/page",
    "/[[...catchAll]]/page",
  ],
  "/nested/[foo]/[bar]/[baz]": [
    "/nested/[foo]/[bar]/@slot/[baz]/page",
    "/[[...catchAll]]/page",
  ],
}
```
This means that when building the `LoaderTree`, it won't ever try to
find the default for that segment. **This solution operates under the
assumption that if you defined a `default` at a particular layout
segment, you intend for that to render in place of a greedier
catch-all.** (Let me know if this is an incorrect assumption)

### How?
We can't safely normalize catch-all parallel routes without having
context about where the `default` segments are, so this updates
`appPaths` to be inclusive of default segments and then filters them
when doing anything relating to build/export to maintain existing
behavior. We use this information to check if an existing default exists
at the same segment level that we'd push the catch-all to. If one
exists, we don't push the catch-all. Otherwise we proceed as normal.

Closes NEXT-1987
2024-01-05 14:20:45 -08:00
Zack Tanner
c4adae89b1
fix parallel catch-all route normalization (#59791)
### What?
Catch-all routes are being matched to parallel routes which causes
issues like an interception route being handled by the wrong page
component, or a page component being associated with multiple pages
resulting in a "You cannot have two parallel pages that resolve to the
same path" build error.

### Why?
#58215 fixed a bug that caused catchall paths to not properly match when
used with parallel routes. In other words, a catchall slot wouldn't
render on a page that could match that catch all. Or a catchall page
wouldn't match a slot. At build time, a normalization step was
introduced to take application paths and attempt to perform this
matching behavior.

However in it's current form, this causes the errors mentioned above to
manifest. To better illustrate the problem, here are a few examples:

Given:
```
{
  '/': [ '/page' ],
  '/[...slug]': [ '/[...slug]/page' ],
  '/items/[...ids]': [ '/items/[...ids]/page' ],
  '/(.)items/[...ids]': [ '/@modal/(.)items/[...ids]/page' ]
}
```

The normalization logic would produce:
```
{
  '/': [ '/page' ],
  '/[...slug]': [ '/[...slug]/page' ],
  '/items/[...ids]': [ '/items/[...ids]/page' ],
  '/(.)items/[...ids]': [ '/@modal/(.)items/[...ids]/page', '/[...slug]/page' ]
}
```
The interception route will now be improperly handled by
`[...slug]/page` rather than the interception handler.

Another example, which rather than incorrectly handling a match, will
produce a build error:

Given:
```
{
  '/': [ '/(group-b)/page' ],
  '/[...catcher]': [ '/(group-a)/@parallel/[...catcher]/page' ]
}
```

The normalization logic would produce:

```
{
  '/': [ '/(group-b)/page', '/(group-a)/@parallel/[...catcher]/page' ],
  '/[...catcher]': [ '/(group-a)/@parallel/[...catcher]/page' ]
}
```
The parallel catch-all slot is now part of `/`. This means when building
the loader tree, two `children` parallel segments (aka page components)
will be found when hitting `/`, which is an error.

The error that was added here was originally intended to help catch
scenarios like:
`/app/(group-a)/page` and `/app/(group-b)/page`. However it also throws
for parallel slots, which isn't necessarily an error (especially since
the normalization logic will push potential matches).

### How?
There are two small fixes in this PR, the rest are an abundance of e2e
tests to help prevent regressions.

- When normalizing catch-all routes, we will not attempt to push any
page entrypoints for interception routes. These should already have all
the information they need in `appPaths`.
- Before throwing the error about duplicate page segments in
`next-app-loader`, we check to see if it's because we already matched a
page component but we also detected a parallel slot that would have
matched the page slot. In this case, we don't error, since the app can
recover from this.
- Loading a client reference manifest shouldn't throw a cryptic require
error. `loadClientReferenceManifest` is already potentially returning
undefined, so this case should already be handled gracefully

Separately, we'll need to follow-up on the Turbopack side to:
- Make sure the duplicate matching matches the Webpack implementation (I
believe Webpack is sorting, but Turbopack isn't)
- Implement #58215 in Turbopack. Once this is done, we should expect the
tests added in this PR to start failing.

Fixes #58272
Fixes #58660
Fixes #58312
Fixes #59782
Fixes #59784

Closes NEXT-1809
2023-12-22 09:30:23 -08:00