Skip to content

perf: navigation blocked on a chunk fetch because prefetch was off - #122

Merged
fas89 merged 1 commit into
mainfrom
fix/prefetch
Sep 14, 2026
Merged

fas89 merged 1 commit into
mainfrom
fix/prefetch

Conversation

@fas89

@fas89 fas89 commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Reported symptom: switching between pages feels slow. Confirmed, with a cause.

The cause

shouldPrefetch and shouldPreload both default to true in VuePress. This repo set both to false in the initial commit, in April, with no comment explaining why.

The cost is paid on every navigation. With prefetch off the built site carries zero <link rel="prefetch"> across 213 pages, so clicking a link blocks on fetching that page's chunk — a full network round trip before anything renders.

prefetch links in dist: 0
js chunks in assets/:   310

My first hypothesis was wrong and worth recording: I assumed it was disabled to stop Monaco being prefetched. git log -S shouldPrefetch shows it predates the playground entirely. There was never a reason.

Why turning it back on would have been worse

Of 19 MB of chunks, 12.4 MB is Monaco — the ts / css / html / json workers plus editor.api and vs — reachable only from /playground/. shouldPrefetch: true would prefetch all of it for every visitor, trading one problem for a worse one.

The fix

The documented function form, (file, type) => boolean: prefetch script chunks, skip the editor. 299 of the 310 chunks are under 100 KB and are exactly the route chunks that make navigation feel instant.

before after
prefetch links per page 0 214
Monaco chunks prefetched — 0
prefetched weight — 4.87 MB, cached after the first page

That is strictly less than the framework default, not more aggressive than it.

shouldPreload returns to true. Preload only covers files the current page needs, so the default is correct everywhere — including /playground/, where Monaco genuinely is required.

Verification

  • Build green; 213 of 219 HTML files carry prefetch links (the 6 without are static reels/*.html assets, not VuePress pages)
  • /playground/ still references its own chunk and not Monaco's in HTML; both worker bundles still exist for on-demand load
  • Monaco exclusion checked by parsing every <link rel="prefetch"> href on a deep page: 0 matches against ts.worker, css.worker, html.worker, json.worker, editor.api, vs-

One honest limit

I could not reproduce the latency locally — served from localhost there is no round trip to pay. The mechanism is certain from the built HTML; the felt improvement needs a real network, which is where it was reported. Worth confirming once this deploys.

Considered and rejected

quicklink (viewport + idle) and instant.page (65 ms hover) both solve this well, but they add a runtime dependency to do what the framework already exposes natively — and neither can see chunk names, so neither could have excluded Monaco, which is the entire difficulty here.

Reported symptom: switching between pages feels slow. Confirmed, with a cause.

`shouldPrefetch` and `shouldPreload` both DEFAULT TO TRUE in VuePress. This repo
set both to false in the initial commit, in April, with no comment. The cost is
paid on every navigation: with prefetch off the built site carries **zero**
`<link rel="prefetch">` across 213 pages, so clicking a link blocks on fetching
that page's chunk - one network round trip before anything renders. Measured on
the built output: 0 prefetch links against 310 chunks.

I could not reproduce the latency locally, and should say so: served from
localhost there is no round trip to pay. The mechanism is certain from the built
HTML; the felt slowness needs a real network, which is where it was reported.

**Turning it fully back on would have been worse.** Of 19 MB of chunks, 12.4 MB
is Monaco - the ts / css / html / json workers plus `editor.api` and `vs` - and
that is reachable only from /playground/. The VuePress default of `true` would
prefetch all of it for every visitor.

So `shouldPrefetch` is now the documented function form, `(file, type) => boolean`,
prefetching script chunks and skipping the editor. Result on a deep page: 214
prefetch links, 4.87 MB, and **0 Monaco chunks**. That is cached after the first
page rather than paid per navigation, and it is strictly less than the framework
default, not more.

`shouldPreload` returns to `true`, its default. Preload only covers files the
CURRENT page needs, so it is correct everywhere including /playground/, where
Monaco genuinely is required.

Verified: the playground still references its chunk and NOT Monaco's in HTML,
both worker bundles still exist for on-demand load, and the build is green.

Considered and rejected: quicklink and instant.page both solve this well, but
they add a runtime dependency to do what the framework already exposes, and
neither can see chunk names - so neither could have excluded Monaco, which is
the whole difficulty here.
@fas89
fas89 merged commit 07befe8 into main Sep 14, 2026
6 checks passed

This branch was successfully deployed

1 active deployment
github-pages — a8fa1d22 Deployed Sep 14, 2026 by fas89 via deploy #151
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant