diff --git a/__tests__/e2e/prefetch/html.md b/__tests__/e2e/prefetch/html.md
new file mode 100644
index 000000000..6a41c3bb2
--- /dev/null
+++ b/__tests__/e2e/prefetch/html.md
@@ -0,0 +1 @@
+# HTML link destination
diff --git a/__tests__/e2e/prefetch/index.md b/__tests__/e2e/prefetch/index.md
new file mode 100644
index 000000000..31582a370
--- /dev/null
+++ b/__tests__/e2e/prefetch/index.md
@@ -0,0 +1,15 @@
+---
+sidebar: false
+outline: false
+---
+
+# Link prefetching
+
+
+
+[HTML link](./html.md)
diff --git a/__tests__/e2e/prefetch/new-tab.md b/__tests__/e2e/prefetch/new-tab.md
new file mode 100644
index 000000000..6a35f9657
--- /dev/null
+++ b/__tests__/e2e/prefetch/new-tab.md
@@ -0,0 +1 @@
+# New tab destination
diff --git a/__tests__/e2e/prefetch/prefetch.test.ts b/__tests__/e2e/prefetch/prefetch.test.ts
new file mode 100644
index 000000000..1a6e7ec9e
--- /dev/null
+++ b/__tests__/e2e/prefetch/prefetch.test.ts
@@ -0,0 +1,43 @@
+test.runIf(process.env.VITE_TEST_BUILD)(
+ 'prefetches SVG links without errors and respects new-tab targets',
+ async () => {
+ const errors: string[] = []
+ const onPageError = (error: Error) => errors.push(error.message)
+ page.on('pageerror', onPageError)
+
+ try {
+ await goto('/prefetch/')
+
+ await expect
+ .poll(
+ async () => ({
+ errors,
+ pages: await page
+ .locator('link[rel="prefetch"]')
+ .evaluateAll((links) =>
+ links
+ .map(
+ (link) =>
+ link
+ .getAttribute('href')
+ ?.match(/prefetch_(.*?)\.md\./)?.[1]
+ )
+ .filter(Boolean)
+ .sort()
+ )
+ }),
+ { timeout: 10_000 }
+ )
+ .toEqual({ errors: [], pages: ['html', 'svg', 'xlink'] })
+
+ await page.locator('svg a[href="./svg.html"]').click()
+ await page.waitForSelector('h1', { state: 'visible' })
+ await expect
+ .poll(() => page.locator('h1').textContent())
+ .toContain('SVG link destination')
+ expect(errors).toEqual([])
+ } finally {
+ page.off('pageerror', onPageError)
+ }
+ }
+)
diff --git a/__tests__/e2e/prefetch/svg.md b/__tests__/e2e/prefetch/svg.md
new file mode 100644
index 000000000..c3eeda434
--- /dev/null
+++ b/__tests__/e2e/prefetch/svg.md
@@ -0,0 +1,3 @@
+# SVG link destination
+
+## Section
diff --git a/__tests__/e2e/prefetch/xlink.md b/__tests__/e2e/prefetch/xlink.md
new file mode 100644
index 000000000..86a76f59c
--- /dev/null
+++ b/__tests__/e2e/prefetch/xlink.md
@@ -0,0 +1 @@
+# SVG xlink destination
diff --git a/src/client/app/composables/preFetch.ts b/src/client/app/composables/preFetch.ts
index 35c23a810..f8085b63c 100644
--- a/src/client/app/composables/preFetch.ts
+++ b/src/client/app/composables/preFetch.ts
@@ -10,6 +10,12 @@ import { inBrowser, pathToFile } from '../utils'
const hasFetched = new Set()
const createLink = () => document.createElement('link')
+const getLinkUrl = (link: HTMLAnchorElement | SVGAElement) =>
+ new URL(
+ link.href instanceof SVGAnimatedString ? link.href.animVal : link.href,
+ link.baseURI
+ )
+
const viaDOM = (url: string) => {
const link = createLink()
link.rel = `prefetch`
@@ -64,9 +70,9 @@ export function usePrefetch() {
observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
- const link = entry.target as HTMLAnchorElement
+ const link = entry.target as HTMLAnchorElement | SVGAElement
observer!.unobserve(link)
- const { pathname } = link
+ const { pathname } = getLinkUrl(link)
if (!hasFetched.has(pathname)) {
hasFetched.add(pathname)
const pageChunkPath = pathToFile(pathname)
@@ -80,12 +86,7 @@ export function usePrefetch() {
document
.querySelectorAll('#app a')
.forEach((link) => {
- const { hostname, pathname } = new URL(
- link.href instanceof SVGAnimatedString
- ? link.href.animVal
- : link.href,
- link.baseURI
- )
+ const { hostname, pathname } = getLinkUrl(link)
const extMatch = pathname.match(/\.\w+$/)
if (extMatch && extMatch[0] !== '.html') {
return
@@ -94,7 +95,7 @@ export function usePrefetch() {
if (
// only prefetch same tab navigation, since a new tab will load
// the lean js chunk instead.
- link.target !== '_blank' &&
+ link.getAttribute('target') !== '_blank' &&
// only prefetch inbound links
hostname === location.hostname
) {