Skip to content

Commit

Permalink
simplify alias handling and add more exhaustive tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ztanner committed Aug 8, 2024
1 parent 896cd58 commit 9e52252
Show file tree
Hide file tree
Showing 7 changed files with 349 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ describe('createInitialRouterState', () => {
lastUsedTime: expect.any(Number),
treeAtTimeOfPrefetch: initialTree,
status: PrefetchCacheEntryStatus.fresh,
pathname: '/linking',
},
],
]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ function getExistingCacheEntry(
// We first check if there's a more specific interception route prefetch entry
// This is because when we detect a prefetch that corresponds with an interception route, we prefix it with nextUrl (see `createPrefetchCacheKey`)
// to avoid conflicts with other pages that may have the same URL but render different things depending on the `Next-URL` header.
let entryWithParams: PrefetchCacheEntry | undefined = undefined

for (const maybeNextUrl of [nextUrl, null]) {
const cacheKeyWithParams = createPrefetchCacheKeyImpl(
url,
Expand All @@ -86,22 +88,9 @@ function getExistingCacheEntry(
// we'll only ever use the `loading` state from the prefetched data, so we can safely re-use the cache entry without search params.
// The check to see if there's a matching prefetch entry with params is important because we don't want to return a param-less response
// if the requested URL has an entry for the provided params.
const entryWithoutParams = prefetchCache.get(cacheKeyWithoutParams)
if (
url.search &&
entryWithoutParams &&
!prefetchCache.has(cacheKeyWithParams) &&
kind !== PrefetchKind.FULL
) {
// Since search params are present, and we're returning the param-less entry, the only thing that should be consumed
// from the cache entry is the `loading` state. If we applied the prefetch as-is, it's possible that a "FULL" param-less
// prefetch could exist, which would trick the router into thinking it should use the RSC data that corresponds with the
// param-less route, rather than the intended behavior, which is to only copy the loading information into the tree and skip the rest
// so that it can be lazily fetched later. This doesn't mutate the original cache entry since the original entry is still
// expected to be used when navigating to the exact URL without params.
// TODO: This is a bit of a hack. Another option to explore is to have a separate cache for loading states,
// which is a bit more aligned with the future goal of per-segment cache entries, but that's a bit more complex.
return { ...entryWithoutParams, aliased: true }
entryWithParams = prefetchCache.get(cacheKeyWithParams)
if (entryWithParams) {
return entryWithParams
}

// We check for the cache entry with search params first, as it's more specific.
Expand All @@ -114,6 +103,19 @@ function getExistingCacheEntry(
}
}

// If we've gotten to this point, we didn't find a specific cache entry that matched
// the request URL.
// We'll now attempt a partial match, by checking if there's a cache entry with the same pathname.
// Regardless of what we find, since it doesn't correspond with the requested URL, we'll mark it "aliased"
// This will signal to the router that it should only apply the loading state on the prefetched data.
if (kind !== PrefetchKind.FULL) {
for (const cacheEntry of prefetchCache.values()) {
if (cacheEntry.pathname === url.pathname) {
return { ...cacheEntry, aliased: true }
}
}
}

return undefined
}

Expand Down Expand Up @@ -246,6 +248,7 @@ export function createPrefetchCacheEntryForInitialLoad({
lastUsedTime: Date.now(),
key: prefetchCacheKey,
status: PrefetchCacheEntryStatus.fresh,
pathname: url.pathname,
} satisfies PrefetchCacheEntry

prefetchCache.set(prefetchCacheKey, prefetchEntry)
Expand Down Expand Up @@ -321,6 +324,7 @@ function createLazyPrefetchEntry({
lastUsedTime: null,
key: prefetchCacheKey,
status: PrefetchCacheEntryStatus.fresh,
pathname: url.pathname,
}

prefetchCache.set(prefetchCacheKey, prefetchEntry)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export type PrefetchCacheEntry = {
lastUsedTime: number | null
key: string
status: PrefetchCacheEntryStatus
usePartialData?: boolean
pathname: string
}

export enum PrefetchCacheEntryStatus {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client'
import Link from 'next/link'
import { useRouter } from 'next/navigation'

export default function Page() {
const router = useRouter()
return (
<>
<div>
<Link href="/search-params?id=1" prefetch>
/search-params?id=1 (prefetch: true)
</Link>
</div>
<button
onClick={() => {
router.push('/search-params')
}}
>
Navigate to /search-params
</button>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client'
import Link from 'next/link'
import { useRouter } from 'next/navigation'

export default function Page() {
const router = useRouter()
return (
<>
<div>
<Link href="/search-params" prefetch>
/search-params (prefetch: true)
</Link>
</div>
<button
onClick={() => {
router.push('/search-params?id=1')
}}
>
Navigate to /search-params?id=1
</button>
</>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use client'
import Link from 'next/link'
import { useRouter } from 'next/navigation'

export default function Page() {
const router = useRouter()
return (
<>
<div>
<Link href="/search-params?id=1" prefetch>
/search-params?id=1 (prefetch: true)
</Link>
</div>
<button
onClick={() => {
router.push('/search-params?id=2')
}}
>
Navigate to /search-params?id=2
</button>
</>
)
}
Loading

0 comments on commit 9e52252

Please sign in to comment.