[object Object]

← 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 →