diff --git a/quartz/components/scripts/popover.inline.ts b/quartz/components/scripts/popover.inline.ts
index 49f4382..b01af0e 100644
--- a/quartz/components/scripts/popover.inline.ts
+++ b/quartz/components/scripts/popover.inline.ts
@@ -1,5 +1,6 @@
import { computePosition, flip, inline, shift } from "@floating-ui/dom"
import { normalizeRelativeURLs } from "../../util/path"
+import { fetchCanonical } from "./util"
const p = new DOMParser()
async function mouseEnterHandler(
@@ -37,7 +38,7 @@ async function mouseEnterHandler(
targetUrl.hash = ""
targetUrl.search = ""
- const response = await fetch(`${targetUrl}`).catch((err) => {
+ const response = await fetchCanonical(targetUrl).catch((err) => {
console.error(err)
})
diff --git a/quartz/components/scripts/spa.inline.ts b/quartz/components/scripts/spa.inline.ts
index b67dad0..df48f04 100644
--- a/quartz/components/scripts/spa.inline.ts
+++ b/quartz/components/scripts/spa.inline.ts
@@ -1,5 +1,6 @@
import micromorph from "micromorph"
import { FullSlug, RelativeURL, getFullSlug, normalizeRelativeURLs } from "../../util/path"
+import { fetchCanonical } from "./util"
// adapted from `micromorph`
// https://github.com/natemoo-re/micromorph
@@ -59,7 +60,7 @@ let p: DOMParser
async function navigate(url: URL, isBack: boolean = false) {
startLoading()
p = p || new DOMParser()
- const contents = await fetch(`${url}`)
+ const contents = await fetchCanonical(url)
.then((res) => {
const contentType = res.headers.get("content-type")
if (contentType?.startsWith("text/html")) {
diff --git a/quartz/components/scripts/util.ts b/quartz/components/scripts/util.ts
index d0a16c6..c1db8ba 100644
--- a/quartz/components/scripts/util.ts
+++ b/quartz/components/scripts/util.ts
@@ -24,3 +24,22 @@ export function removeAllChildren(node: HTMLElement) {
node.removeChild(node.firstChild)
}
}
+
+// AliasRedirect emits HTML redirects which also have the link[rel="canonical"]
+// containing the URL it's redirecting to.
+// Extracting it here with regex is _probably_ faster than parsing the entire HTML
+// with a DOMParser effectively twice (here and later in the SPA code), even if
+// way less robust - we only care about our own generated redirects after all.
+const canonicalRegex = //
+
+export async function fetchCanonical(url: URL): Promise {
+ const res = await fetch(`${url}`)
+ if (!res.headers.get("content-type")?.startsWith("text/html")) {
+ return res
+ }
+ // reading the body can only be done once, so we need to clone the response
+ // to allow the caller to read it if it's was not a redirect
+ const text = await res.clone().text()
+ const [_, redirect] = text.match(canonicalRegex) ?? []
+ return redirect ? fetch(redirect) : res
+}