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

fix(nuxt): defer render-blocking prefetches until after load #9475

Merged
merged 4 commits into from
Dec 2, 2022
Merged
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
35 changes: 21 additions & 14 deletions packages/nuxt/src/app/components/nuxt-link.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,23 +193,30 @@ export function defineNuxtLink (options: NuxtLinkOptions) {
const shouldPrefetch = props.prefetch !== false && props.noPrefetch !== true && typeof to.value === 'string' && props.target !== '_blank' && !isSlowConnection()
if (shouldPrefetch) {
const nuxtApp = useNuxtApp()
const observer = useObserver()
let idleId: number
let unobserve: Function | null = null
onMounted(() => {
idleId = requestIdleCallback(() => {
if (el?.value?.tagName) {
unobserve = observer!.observe(el.value, async () => {
unobserve?.()
unobserve = null
await Promise.all([
nuxtApp.hooks.callHook('link:prefetch', to.value as string).catch(() => {}),
!isExternal.value && preloadRouteComponents(to.value as string, router).catch(() => {})
])
prefetched.value = true
})
}
})
const observer = useObserver()
function registerCallback () {
idleId = requestIdleCallback(() => {
if (el?.value?.tagName) {
unobserve = observer!.observe(el.value, async () => {
unobserve?.()
unobserve = null
await Promise.all([
nuxtApp.hooks.callHook('link:prefetch', to.value as string).catch(() => {}),
!isExternal.value && preloadRouteComponents(to.value as string, router).catch(() => {})
])
prefetched.value = true
})
}
})
}
if (nuxtApp.isHydrating) {
nuxtApp.hooks.hookOnce('app:suspense:resolve', registerCallback)
} else {
registerCallback()
}
})
onBeforeUnmount(() => {
if (idleId) { cancelIdleCallback(idleId) }
Expand Down
2 changes: 2 additions & 0 deletions packages/nuxt/src/app/nuxt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ export function createNuxtApp (options: CreateOptions) {

if (hydratingCount === 0) {
nuxtApp.isHydrating = false
// @ts-expect-error private flag
globalThis.__hydrated = true
return nuxtApp.callHook('app:suspense:resolve')
}
}
Expand Down
17 changes: 14 additions & 3 deletions packages/vite/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,38 @@
import { join, resolve } from 'pathe'
import { join, relative, resolve } from 'pathe'
import * as vite from 'vite'
import vuePlugin from '@vitejs/plugin-vue'
import viteJsxPlugin from '@vitejs/plugin-vue-jsx'
import type { ServerOptions } from 'vite'
import { logger } from '@nuxt/kit'
import { getPort } from 'get-port-please'
import { joinURL, withoutLeadingSlash } from 'ufo'
import { joinURL, withoutLeadingSlash, withoutTrailingSlash } from 'ufo'
import defu from 'defu'
import type { OutputOptions } from 'rollup'
import { defineEventHandler } from 'h3'
import { genString } from 'knitwork'
import { cacheDirPlugin } from './plugins/cache-dir'
import type { ViteBuildContext, ViteOptions } from './vite'
import { devStyleSSRPlugin } from './plugins/dev-ssr-css'
import { viteNodePlugin } from './vite-node'

export async function buildClient (ctx: ViteBuildContext) {
const buildAssetsDir = withoutLeadingSlash(
withoutTrailingSlash(ctx.nuxt.options.app.buildAssetsDir)
)
const relativeToBuildAssetsDir = (filename: string) => './' + relative(buildAssetsDir, filename)
const clientConfig: vite.InlineConfig = vite.mergeConfig(ctx.config, {
entry: ctx.entry,
base: ctx.nuxt.options.dev
? joinURL(ctx.nuxt.options.app.baseURL.replace(/^\.\//, '/') || '/', ctx.nuxt.options.app.buildAssetsDir)
: './',
experimental: {
renderBuiltUrl: (filename, { type, hostType }) => {
renderBuiltUrl: (filename, { type, hostType, hostId }) => {
// When rendering inline styles, we can skip loading CSS chunk that matches the current page
if (ctx.nuxt.options.experimental.inlineSSRStyles && hostType === 'js' && filename.endsWith('.css')) {
return {
runtime: `!globalThis.__hydrated ? ${genString(relativeToBuildAssetsDir(hostId))} : ${genString(relativeToBuildAssetsDir(filename))}`
}
}
if (hostType !== 'js' || type === 'asset') {
// In CSS we only use relative paths until we craft a clever runtime CSS hack
return { relative: true }
Expand Down