← back to AsSeenInMovies
sitemap: extract buildSitemapXml() helper — coalesce concurrent cold-cache builds; /sitemap.txt no longer returns 'warming' stub
8243fbec43455f378684fa3c6e04c1578ff4cfd1 · 2026-05-13 11:22:29 -0700 · SteveStudio2
Files touched
Diff
commit 8243fbec43455f378684fa3c6e04c1578ff4cfd1
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Wed May 13 11:22:29 2026 -0700
sitemap: extract buildSitemapXml() helper — coalesce concurrent cold-cache builds; /sitemap.txt no longer returns 'warming' stub
---
server.js | 56 +++++++++++++++++++++++++++++---------------------------
1 file changed, 29 insertions(+), 27 deletions(-)
diff --git a/server.js b/server.js
index 80a3686..b164967 100644
--- a/server.js
+++ b/server.js
@@ -1936,18 +1936,16 @@ ${process.env.GA_ID ? `<script async src="https://www.googletagmanager.com/gtag/
// doesn't hit on every crawler probe.
// ────────────────────────────────────────────────────────────────────
let _sitemapCache = { at: 0, xml: null };
+let _sitemapInflight = null;
const SITEMAP_TTL_MS = 10 * 60 * 1000;
-app.get('/sitemap.xml', async (_req, res) => {
- const now = Date.now();
- if (_sitemapCache.xml && (now - _sitemapCache.at) < SITEMAP_TTL_MS) {
- // Cache hit — preserve the same headers as the cache-miss path so caches
- // and CDNs see consistent Last-Modified / Cache-Control regardless of
- // whether this request happened to warm the cache.
- res.setHeader('Cache-Control', 'public, max-age=21600, stale-while-revalidate=86400');
- res.setHeader('Last-Modified', new Date(_sitemapCache.at).toUTCString());
- return res.type('application/xml').send(_sitemapCache.xml);
- }
- try {
+
+// Build the sitemap XML, populate the cache, and return it. Coalesces concurrent
+// requests through _sitemapInflight so a cold cache + 2 simultaneous crawler
+// hits trigger one DB pass, not two. Used by /sitemap.xml AND /sitemap.txt so
+// they stay consistent and /sitemap.txt never returns a "warming" stub.
+async function buildSitemapXml() {
+ if (_sitemapInflight) return _sitemapInflight;
+ _sitemapInflight = (async () => {
const base = process.env.ASIM_PUBLIC_URL || 'https://asseeninmovies.com';
const today = new Date().toISOString().slice(0, 10);
const STATIC = [
@@ -1976,16 +1974,26 @@ app.get('/sitemap.xml', async (_req, res) => {
for (const p of peopleR.rows) {
urls.push(`<url><loc>${base}/person/${p.id}</loc><lastmod>${today}</lastmod><changefreq>monthly</changefreq><priority>0.5</priority></url>`);
}
- // Each verified spotted lives under its movie's page — surface the movie URL
- // anchored to #spotted-<id> so crawlers can discover them individually.
for (const s of spottedR.rows) {
urls.push(`<url><loc>${base}/movie/${s.movie_id}#spotted-${s.id}</loc><lastmod>${today}</lastmod><changefreq>weekly</changefreq><priority>0.7</priority></url>`);
}
const xml = `<?xml version="1.0" encoding="UTF-8"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n ${urls.join('\n ')}\n</urlset>`;
- _sitemapCache = { at: now, xml };
+ _sitemapCache = { at: Date.now(), xml };
+ return _sitemapCache;
+ })();
+ try { return await _sitemapInflight; }
+ finally { _sitemapInflight = null; }
+}
+
+app.get('/sitemap.xml', async (_req, res) => {
+ const now = Date.now();
+ try {
+ const cache = (_sitemapCache.xml && (now - _sitemapCache.at) < SITEMAP_TTL_MS)
+ ? _sitemapCache
+ : await buildSitemapXml();
res.setHeader('Cache-Control', 'public, max-age=21600, stale-while-revalidate=86400');
- res.setHeader('Last-Modified', new Date(now).toUTCString());
- res.type('application/xml').send(xml);
+ res.setHeader('Last-Modified', new Date(cache.at).toUTCString());
+ res.type('application/xml').send(cache.xml);
} catch (e) {
res.status(500).type('text/plain').send(`error: ${e.message}`);
}
@@ -2116,18 +2124,12 @@ app.get('/favicon.ico', (_req, res) => {
// prefer this; some tools (Bing Webmaster) accept it as fallback.
app.get('/sitemap.txt', async (_req, res) => {
try {
- if (!_sitemapCache.xml || (Date.now() - _sitemapCache.at) > SITEMAP_TTL_MS) {
- // Force XML regeneration to fill cache; we'll extract URLs from it.
- // Cheaper than re-querying.
- }
- const xml = _sitemapCache.xml;
- if (!xml) {
- res.setHeader('Cache-Control', 'public, max-age=60');
- return res.type('text/plain').send('# sitemap warming\n');
- }
- const urls = (xml.match(/<loc>([^<]+)<\/loc>/g) || []).map(m => m.slice(5, -6));
+ const cache = (_sitemapCache.xml && (Date.now() - _sitemapCache.at) < SITEMAP_TTL_MS)
+ ? _sitemapCache
+ : await buildSitemapXml();
+ const urls = (cache.xml.match(/<loc>([^<]+)<\/loc>/g) || []).map(m => m.slice(5, -6));
res.setHeader('Cache-Control', 'public, max-age=21600, stale-while-revalidate=86400');
- res.setHeader('Last-Modified', new Date(_sitemapCache.at || Date.now()).toUTCString());
+ res.setHeader('Last-Modified', new Date(cache.at).toUTCString());
res.type('text/plain').send(urls.join('\n') + '\n');
} catch (e) {
res.status(500).type('text/plain').send(`error: ${e.message}`);
← 888e73e smoke: cover /healthz (200, ok JSON, no-store, HEAD method)
·
back to AsSeenInMovies
·
smoke: assert /api/spotted/featured SWR Cache-Control aea2c77 →