From 9c7981a843d22705282c00c68f57febdc96690d4 Mon Sep 17 00:00:00 2001 From: Dmitry Verkhoturov Date: Tue, 26 May 2026 18:37:32 +0100 Subject: [PATCH] site: cache latest version in sessionStorage with 1h TTL MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit avoids hitting the GitHub API on every page load — repeated navigations within a tab read from sessionStorage instead. TTL caps stale display at 1h for very long-lived tabs. cleared on tab close, so each new session fetches once and reuses the result throughout. --- site/src/includes/inline.js | 80 ++++++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 23 deletions(-) diff --git a/site/src/includes/inline.js b/site/src/includes/inline.js index 9526f3ab..f7010d63 100644 --- a/site/src/includes/inline.js +++ b/site/src/includes/inline.js @@ -6,26 +6,60 @@ if ((theme && theme === 'dark') || (!theme && mq.matches)) { } // fetch the latest release version client-side so the badge tracks releases -// without needing a site rebuild. GitHub serves this with Cache-Control: -// public, max-age=60 so the per-visitor cost is bounded. placeholder is -// `hidden` in the template so a failed/blocked fetch leaves no stray gap. -fetch('https://api.github.com/repos/umputun/remark42/releases/latest') - .then((r) => (r.ok ? r.json() : Promise.reject(new Error('HTTP ' + r.status)))) - .then((d) => { - if (!d || !d.tag_name) return - // script is loaded synchronously in , so a cache hit can resolve - // before is parsed and [data-remark42-version] exists. defer the - // DOM update until the document is ready. - const apply = () => { - document.querySelectorAll('[data-remark42-version]').forEach((el) => { - el.textContent = d.tag_name - el.hidden = false - }) - } - if (document.readyState === 'loading') { - document.addEventListener('DOMContentLoaded', apply) - } else { - apply() - } - }) - .catch((err) => console.warn('remark42-site: latest version fetch failed', err)) +// without needing a site rebuild. cached in sessionStorage for up to 1h so +// repeated navigations within a tab don't re-hit the GitHub API on every page +// load. placeholder is `hidden` in the template so a failed/blocked fetch +// leaves no stray gap. +const versionCacheKey = 'remark42-latest-version' +const versionCacheTTL = 60 * 60 * 1000 + +// script is loaded synchronously in , so a cache-hit fetch can resolve +// before is parsed and [data-remark42-version] exists. defer the DOM +// update until the document is ready. +function applyVersion(tag) { + const update = () => { + document.querySelectorAll('[data-remark42-version]').forEach((el) => { + el.textContent = tag + el.hidden = false + }) + } + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', update) + } else { + update() + } +} + +function readVersionCache() { + try { + const raw = sessionStorage.getItem(versionCacheKey) + if (!raw) return null + const { tag, expires } = JSON.parse(raw) + if (!tag || typeof expires !== 'number' || Date.now() > expires) return null + return tag + } catch (e) { + return null + } +} + +function writeVersionCache(tag) { + try { + sessionStorage.setItem(versionCacheKey, JSON.stringify({ tag, expires: Date.now() + versionCacheTTL })) + } catch (e) { + // sessionStorage unavailable or full — fall through, fetch will run next load + } +} + +const cachedVersion = readVersionCache() +if (cachedVersion) { + applyVersion(cachedVersion) +} else { + fetch('https://api.github.com/repos/umputun/remark42/releases/latest') + .then((r) => (r.ok ? r.json() : Promise.reject(new Error('HTTP ' + r.status)))) + .then((d) => { + if (!d || !d.tag_name) return + writeVersionCache(d.tag_name) + applyVersion(d.tag_name) + }) + .catch((err) => console.warn('remark42-site: latest version fetch failed', err)) +}