Fix page meta not updating on client-side post navigation

When a card was clicked, history.pushState changed the URL but left
document.title, description, og:*, and canonical pointing at the
overview page. navigate() also only updated document.title, not meta.

Add setMeta/syncHeadMeta helpers; update title+meta from POSTS data on
card open, and sync all head meta from the fetched document in navigate().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-16 10:00:45 +02:00
parent 9b8f03d4ff
commit 9900b193f6
+42
View File
@@ -20,6 +20,36 @@
return esc(text).replace(re, '<mark class="hl">$1</mark>'); return esc(text).replace(re, '<mark class="hl">$1</mark>');
} }
function setMeta(attr, key, val) {
let el = document.querySelector(`meta[${attr}="${key}"]`);
if (val !== null && val !== undefined && val !== '') {
if (!el) { el = document.createElement('meta'); el.setAttribute(attr, key); document.head.appendChild(el); }
el.setAttribute('content', val);
} else if (el) {
el.remove();
}
}
function syncHeadMeta(doc) {
const canon = doc.querySelector('link[rel="canonical"]');
const curCanon = document.querySelector('link[rel="canonical"]');
if (canon && curCanon) curCanon.href = canon.href;
[
['name', 'description'],
['property', 'og:title'],
['property', 'og:description'],
['property', 'og:url'],
['property', 'og:image'],
['property', 'og:image:width'],
['property', 'og:image:height'],
['name', 'twitter:card'],
['name', 'twitter:image'],
].forEach(([attr, key]) => {
const src = doc.querySelector(`meta[${attr}="${key}"]`);
setMeta(attr, key, src ? src.getAttribute('content') : null);
});
}
// ── Inverted search index // ── Inverted search index
const INDEX = (() => { const INDEX = (() => {
const map = new Map(); const map = new Map();
@@ -377,6 +407,17 @@
const scopedList = issueId ? POSTS.filter(p => p.issue === issueId) : POSTS; const scopedList = issueId ? POSTS.filter(p => p.issue === issueId) : POSTS;
lbReferrer = location.href; lbReferrer = location.href;
history.pushState({ slug }, '', card.href); history.pushState({ slug }, '', card.href);
if (clicked) {
const postTitle = clicked.title + ' — Roux';
const postUrl = new URL(clicked.url, location.origin).href;
document.title = postTitle;
setMeta('name', 'description', clicked.description || null);
setMeta('property', 'og:title', postTitle);
setMeta('property', 'og:description', clicked.description || null);
setMeta('property', 'og:url', postUrl);
const canon = document.querySelector('link[rel="canonical"]');
if (canon) canon.href = postUrl;
}
lbOpen(slug, scopedList.length ? scopedList : POSTS); lbOpen(slug, scopedList.length ? scopedList : POSTS);
}); });
@@ -431,6 +472,7 @@
} }
document.title = doc.title; document.title = doc.title;
syncHeadMeta(doc);
history.pushState({}, '', url); history.pushState({}, '', url);
// Re-init page state // Re-init page state