Skip to content
This repository has been 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
19 changes: 14 additions & 5 deletions packages/nuxt/src/app/components/nuxt-link.ts
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))
danielroe marked this conversation as resolved.
Show resolved Hide resolved
}

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
@@ -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)
danielroe marked this conversation as resolved.
Show resolved Hide resolved
}
})
})
5 changes: 5 additions & 0 deletions packages/nuxt/src/core/nuxt.ts
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
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
}
})