Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion packages/next/errors.json
Original file line number Diff line number Diff line change
Expand Up @@ -847,5 +847,7 @@
"846": "Route %s used \\`cookies()\\` inside a function cached with \\`unstable_cache()\\`. Accessing Dynamic data sources inside a cache scope is not supported. If you need this data inside a cached function use \\`cookies()\\` outside of the cached function and pass the required dynamic data in as an argument. See more info here: https://nextjs.org/docs/app/api-reference/functions/unstable_cache",
"847": "Route %s with \\`dynamic = \"error\"\\` couldn't be rendered statically because it used \\`connection()\\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering",
"848": "%sused %s. \\`searchParams\\` is a Promise and must be unwrapped with \\`await\\` or \\`React.use()\\` before accessing its properties. Learn more: https://nextjs.org/docs/messages/sync-dynamic-apis",
"849": "Route %s with \\`dynamic = \"error\"\\` couldn't be rendered statically because it used \\`cookies()\\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering"
"849": "Route %s with \\`dynamic = \"error\"\\` couldn't be rendered statically because it used \\`cookies()\\`. See more info here: https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic#dynamic-rendering",
"850": "No reference found for param: %s in reference: %s",
"851": "No reference found for segment: %s with reference: %s"
}
5 changes: 3 additions & 2 deletions packages/next/src/client/components/app-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,7 @@ function Router({
}
}, [])

const { cache, tree, nextUrl, focusAndScrollRef } = state
const { cache, tree, nextUrl, focusAndScrollRef, lastNextUrl } = state

const matchingHead = useMemo(() => {
return findHeadInCache(cache, tree[1])
Expand All @@ -423,8 +423,9 @@ function Router({
tree,
focusAndScrollRef,
nextUrl,
lastNextUrl,
}
}, [tree, focusAndScrollRef, nextUrl])
}, [tree, focusAndScrollRef, nextUrl, lastNextUrl])

let head
if (matchingHead !== null) {
Expand Down
9 changes: 8 additions & 1 deletion packages/next/src/client/components/layout-router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,14 @@ function InnerLayoutRouter({
new URL(url, location.origin),
{
flightRouterState: refetchTree,
nextUrl: includeNextUrl ? context.nextUrl : null,
nextUrl: includeNextUrl
? // We always send the last next-url, not the current when
// performing a dynamic request. This is because we update
// the next-url after a navigation, but we want the same
// interception route to be matched that used the last
// next-url.
context.lastNextUrl || context.nextUrl
: null,
}
).then((serverResponse) => {
startTransition(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ describe('createInitialRouterState', () => {
},
cache: expectedCache,
nextUrl: '/linking',
lastNextUrl: null,
}

expect(state).toMatchObject(expected)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ export function createInitialRouterState({
// the || operator is intentional, the pathname can be an empty string
(extractPathFromFlightRouterState(initialTree) || location?.pathname) ??
null,
lastNextUrl: null,
}

if (process.env.NODE_ENV !== 'development' && location) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@ export function handleMutable(
// shouldScroll is true by default, can override to false.
const shouldScroll = mutable.shouldScroll ?? true

let lastNextUrl = state.lastNextUrl
let nextUrl = state.nextUrl

if (isNotUndefined(mutable.patchedTree)) {
// If we received a patched tree, we need to compute the changed path.
const changedPath = computeChangedPath(state.tree, mutable.patchedTree)
if (changedPath) {
// If the tree changed, we need to update the nextUrl
lastNextUrl = nextUrl
nextUrl = changedPath
} else if (!nextUrl) {
// if the tree ends up being the same (ie, no changed path), and we don't have a nextUrl, then we should use the canonicalUrl
Expand Down Expand Up @@ -84,5 +86,6 @@ export function handleMutable(
? mutable.patchedTree
: state.tree,
nextUrl,
lastNextUrl,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,12 @@ export function navigateReducer(
new URL(updatedCanonicalUrl, url.origin),
{
flightRouterState: dynamicRequestTree,
nextUrl: state.nextUrl,
// We always send the last next-url, not the current when
// performing a dynamic request. This is because we update
// the next-url after a navigation, but we want the same
// interception route to be matched that used the last
// next-url.
nextUrl: state.lastNextUrl || state.nextUrl,
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ export function restoreReducer(
// Restore provided tree
tree: treeToRestore,
nextUrl: extractPathFromFlightRouterState(treeToRestore) ?? url.pathname,
lastNextUrl: null,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,14 @@ export function serverActionReducer(
// Otherwise the server action might be intercepted with the wrong action id
// (ie, one that corresponds with the intercepted route)
const nextUrl =
state.nextUrl && hasInterceptionRouteInCurrentTree(state.tree)
? state.nextUrl
// We always send the last next-url, not the current when
// performing a dynamic request. This is because we update
// the next-url after a navigation, but we want the same
// interception route to be matched that used the last
// next-url.
(state.lastNextUrl || state.nextUrl) &&
hasInterceptionRouteInCurrentTree(state.tree)
? state.lastNextUrl || state.nextUrl
: null

const navigatedAt = Date.now()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ export type AppRouterState = {
* The underlying "url" representing the UI state, which is used for intercepting routes.
*/
nextUrl: string | null

/**
* The last next-url that was used previous to a dynamic navigation.
*/
lastNextUrl: string | null
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: i find previousNextUrl clearer if the meaning is "the nextUrl prior to the current one", since last could also imply the "final" URL.

}

export type ReadonlyReducerState = Readonly<AppRouterState>
Expand Down
14 changes: 13 additions & 1 deletion packages/next/src/lib/build-custom-route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,19 @@ export function buildCustomRoute(
)
}

const regex = normalizeRouteRegex(source)
// If this is an internal rewrite and it already provides a regex, use it
// otherwise, normalize the source to a regex.
let regex: string
if (
!route.internal ||
type !== 'rewrite' ||
!('regex' in route) ||
typeof route.regex !== 'string'
) {
regex = normalizeRouteRegex(source)
} else {
regex = route.regex
}

if (type !== 'redirect') {
return { ...route, regex }
Expand Down
Loading
Loading