Skip to content
This repository was archived by the owner on Apr 6, 2023. It is now read-only.

perf(nuxt): improve link prefetching #8225

Merged
merged 7 commits into from
Oct 17, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
19 changes: 14 additions & 5 deletions packages/nuxt/src/app/components/nuxt-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
const el = process.server ? undefined : ref<HTMLElement | null>(null)
if (process.client) {
checkPropConflicts(props, 'prefetch', 'noPrefetch')
const shouldPrefetch = props.prefetch !== false && props.noPrefetch !== true && typeof to.value === 'string' && !isSlowConnection()
const shouldPrefetch = props.prefetch !== false && props.noPrefetch !== true && typeof to.value === 'string' && props.target !== '_blank' && !isSlowConnection()
if (shouldPrefetch) {
const nuxtApp = useNuxtApp()
const observer = useObserver()
Expand Down Expand Up @@ -269,7 +269,7 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
})
}

return h('a', { href, rel, target }, slots.default?.())
return h('a', { ref: el, href, rel, target }, slots.default?.())
}
}
}) as unknown as DefineComponent<NuxtLinkProps>
Expand Down Expand Up @@ -329,21 +329,30 @@ function isSlowConnection () {
return false
}

async function preloadRouteComponents (to: string, router: Router & { _nuxtLinkPreloaded?: Set<string> } = useRouter()) {
async function preloadRouteComponents (to: string, router: Router & { _nuxtLinkPreloaded?: Set<string>; _preloadPromises?: Array<Promise<any>> } = useRouter()): Promise<void> {
if (process.server) { return }

if (!router._nuxtLinkPreloaded) { router._nuxtLinkPreloaded = new Set() }
if (router._nuxtLinkPreloaded.has(to)) { return }
router._nuxtLinkPreloaded.add(to)

const promises = router._preloadPromises ||= []

if (promises.length > 4) {
// Defer adding new preload requests until the existing ones have resolved
return Promise.all(promises).then(() => preloadRouteComponents(to, router))
}

const components = router.resolve(to).matched
.map(component => component.components?.default)
.filter(component => typeof component === 'function')

const promises: Promise<any>[] = []
for (const component of components) {
const promise = Promise.resolve((component as Function)()).catch(() => {})
const promise = Promise.resolve((component as Function)())
.catch(() => {})
.finally(() => promises.splice(promises.indexOf(promise)))
promises.push(promise)
}

await Promise.all(promises)
}
29 changes: 29 additions & 0 deletions packages/nuxt/src/app/plugins/cross-origin-prefetch.client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ref } from 'vue'
import { parseURL } from 'ufo'
import { defineNuxtPlugin, useHead } from '#app'

export default defineNuxtPlugin((nuxtApp) => {
const externalURLs = ref(new Set<string>())
useHead({
script: [
() => ({
type: 'speculationrules',
innerHTML: JSON.stringify({
prefetch: [
{
source: 'list',
urls: [...externalURLs.value],
requires: ['anonymous-client-ip-when-cross-origin']
}
]
})
})
]
})
nuxtApp.hook('link:prefetch', (url) => {
const { protocol } = parseURL(url)
if (!protocol || ['http:', 'https:'].includes(protocol)) {
externalURLs.value.add(url)
}
})
})
5 changes: 5 additions & 0 deletions packages/nuxt/src/core/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@ async function initNuxt (nuxt: Nuxt) {
addPlugin(resolve(nuxt.options.appDir, 'plugins/payload.client'))
}

// Add experimental cross-origin prefetch support using Speculation Rules API
if (nuxt.options.experimental.crossOriginPrefetch) {
addPlugin(resolve(nuxt.options.appDir, 'plugins/cross-origin-prefetch.client'))
}

// Track components used to render for webpack
if (nuxt.options.builder === '@nuxt/webpack-builder') {
addPlugin(resolve(nuxt.options.appDir, 'plugins/preload.server'))
Expand Down
3 changes: 3 additions & 0 deletions packages/schema/src/config/experimental.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,5 +79,8 @@ export default defineUntypedSchema({
* When this option is enabled (by default) payload of pages generated with `nuxt generate` are extracted
*/
payloadExtraction: true,

/** Enable cross-origin prefetch using the Speculation Rules API. */
crossOriginPrefetch: false
}
})