forked from vercel/next.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
app-router: prefetching tweaks (vercel#52949)
This PR tries to address some feedback around prefetching, like in vercel#49607, where they were some warnings because we were over prefetching. The tweaks in this PR: - if there are no loading boundary below, we don't prefetch the full page anymore. I made that change a while ago but I think it wasn't the original intent from @sebmarkbage. Fixing that now. So now, we will prefetch the page content until the nearest loading boundary, only if there is any. - there's now a queue for limiting the number of concurrent prefetches. This is to not ruin the bandwidth for complex pages. Thanks @alvarlagerlof for that one. - also, if the prefetch is in the queue when navigating, it will get bumped. - bonus: fixes a bug where we were wrongly stripping headers in dev for static pages Test plan: <img width="976" alt="CleanShot 2023-07-20 at 17 53 43@2x" src="https://github.com/vercel/next.js/assets/11064311/2ea34419-c002-4aea-94df-57576e3ecb2e"> In the screenshot you can see that: - only 5 requests at a time are authorised - when I clicked on 15, it got prioritised in the queue (did not cancel the rest) - the prefetch only included the content until the loading boundary Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
- Loading branch information
1 parent
cb24c55
commit 55eebef
Showing
7 changed files
with
193 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { PromiseQueue } from './promise-queue' | ||
|
||
describe('PromiseQueue', () => { | ||
it('should limit the number of concurrent promises', async () => { | ||
const queue = new PromiseQueue(2) | ||
const results: number[] = [] | ||
|
||
const promises = Array.from({ length: 5 }, (_, i) => | ||
queue.enqueue(async () => { | ||
results.push(i) | ||
await new Promise((resolve) => setTimeout(resolve, 100)) | ||
return i | ||
}) | ||
) | ||
|
||
const resolved = await Promise.all(promises) | ||
|
||
expect(resolved).toEqual([0, 1, 2, 3, 4]) | ||
expect(results).toEqual([0, 1, 2, 3, 4]) | ||
}) | ||
it('should allow bumping a promise to be next in the queue', async () => { | ||
const queue = new PromiseQueue(2) | ||
const results: number[] = [] | ||
|
||
const promises = Array.from({ length: 5 }, (_, i) => | ||
queue.enqueue(async () => { | ||
results.push(i) | ||
await new Promise((resolve) => setTimeout(resolve, 100)) | ||
return i | ||
}) | ||
) | ||
|
||
queue.bump(promises[3]) | ||
|
||
const resolved = await Promise.all(promises) | ||
|
||
// 3 was bumped to be next in the queue but did not cancel the other promises before it | ||
expect(results).toEqual([0, 1, 3, 2, 4]) | ||
expect(resolved).toEqual([0, 1, 2, 3, 4]) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
This is a simple promise queue that allows you to limit the number of concurrent promises | ||
that are running at any given time. It's used to limit the number of concurrent | ||
prefetch requests that are being made to the server but could be used for other | ||
things as well. | ||
*/ | ||
export class PromiseQueue { | ||
#maxConcurrency: number | ||
#runningCount: number | ||
#queue: Array<{ promiseFn: Promise<any>; task: () => void }> | ||
|
||
constructor(maxConcurrency = 5) { | ||
this.#maxConcurrency = maxConcurrency | ||
this.#runningCount = 0 | ||
this.#queue = [] | ||
} | ||
|
||
enqueue<T>(promiseFn: () => Promise<T>): Promise<T> { | ||
let taskResolve: (value: T | PromiseLike<T>) => void | ||
let taskReject: (reason?: any) => void | ||
|
||
const taskPromise = new Promise((resolve, reject) => { | ||
taskResolve = resolve | ||
taskReject = reject | ||
}) as Promise<T> | ||
|
||
const task = async () => { | ||
try { | ||
this.#runningCount++ | ||
const result = await promiseFn() | ||
taskResolve(result) | ||
} catch (error) { | ||
taskReject(error) | ||
} finally { | ||
this.#runningCount-- | ||
this.#processNext() | ||
} | ||
} | ||
|
||
const enqueueResult = { promiseFn: taskPromise, task } | ||
// wonder if we should take a LIFO approach here | ||
this.#queue.push(enqueueResult) | ||
this.#processNext() | ||
|
||
return taskPromise | ||
} | ||
|
||
bump(promiseFn: Promise<any>) { | ||
const index = this.#queue.findIndex((item) => item.promiseFn === promiseFn) | ||
|
||
if (index > -1) { | ||
const bumpedItem = this.#queue.splice(index, 1)[0] | ||
this.#queue.unshift(bumpedItem) | ||
this.#processNext(true) | ||
} | ||
} | ||
|
||
#processNext(forced = false) { | ||
if ( | ||
(this.#runningCount < this.#maxConcurrency || forced) && | ||
this.#queue.length > 0 | ||
) { | ||
this.#queue.shift()?.task() | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters