Files
pw/scripts/indexnow.mjs
T
Asger Geel Weirsøe 034e5e3312
Build & Deploy / deploy (push) Successful in 2m19s
seo: præcis sitemap-lastmod, IndexNow-ping ved deploy og favicon.ico
Fire ugers adgangslog (81k requests) viser at ægte Googlebot kun hentede robots.txt,
favicon og et par billeder — plus én enkelt HTML-side i hele perioden. Intet af det her
løser dét (kun indgående links gør), men det fjerner de undskyldninger vi selv havde
lagt ud.

- Sitemappet havde ingen lastmod overhovedet. Nu udleder astro.config.mjs den pr. side:
  statiske sider fra deres sidste commit, blogindlæg fra updated/date i frontmatter,
  og oversigts-/tagsider fra det nyeste indlæg de viser. Kan en dato ikke fastslås
  præcist, udelades lastmod helt — en forkert dato er værre end ingen, fordi Google
  holder op med at stole på feltet.
- Det kræver rigtig git-historik, så checkout kører nu fetch-depth: 0 (default shallow
  ville give alle filer samme dato).
- Nyt deploy-step pinger IndexNow med de sider dette push faktisk ændrede — udregnet af
  scripts/indexnow.mjs ud fra diffen, ikke hele sitemappet, da gentagne fuld-lister er
  støj. Nøglefilen lå allerede i public/, men blev aldrig brugt. Hjælper kun Bing/Yandex;
  Google understøtter ikke IndexNow. continue-on-error, så det aldrig vælter et deploy.
- /favicon.ico gav 404. Tilføjet som rigtig 3-størrelses ICO (16/32/48) genereret fra
  favicon.svg med samme baggrund som de øvrige ikoner.

Claude-Session: https://claude.ai/code/session_018oXJWTRxTYdcV9Kw6M7e5J
2026-08-09 09:15:38 +02:00

100 lines
3.3 KiB
JavaScript

// Pinger IndexNow (Bing/Yandex/Seznam m.fl.) med de sider dette push faktisk ændrede.
//
// Vi sender bevidst IKKE hele sitemappet hver gang: IndexNow er til "det her ændrede sig
// nu", og gentagne fuld-lister ser ud som støj. Kan vi ikke regne diffen ud, springer vi
// over frem for at gætte.
//
// Kør efter `astro build` — dist/sitemap-0.xml bruges som facitliste over gyldige URL'er.
// Google understøtter ikke IndexNow; det her hjælper kun de øvrige motorer.
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
const HOST = 'asger.xn--weirse-fya.dk';
const KEY = 'b68b31655cb9c1a662d53905dd0b1b06';
const ORIGIN = `https://${HOST}`;
const git = (args) => {
try {
return execFileSync('git', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
} catch {
return null;
}
};
const sitemapUrls = () => {
const xml = fs.readFileSync('dist/sitemap-0.xml', 'utf8');
return new Set([...xml.matchAll(/<loc>([^<]+)<\/loc>/g)].map((m) => m[1]));
};
// src/pages/foo.astro -> /foo/ ; src/content/blog/bar.md -> /blog/bar/
const routeForFile = (f) => {
let m = f.match(/^src\/pages\/(.+)\.astro$/);
if (m) {
if (m[1].includes('[')) return null; // dynamiske ruter kan vi ikke slå op
if (m[1] === 'index') return '/';
return `/${m[1].replace(/\/index$/, '')}/`;
}
m = f.match(/^src\/content\/blog\/(.+)\.md$/);
if (m) return `/blog/${m[1]}/`;
return null;
};
const changedFiles = () => {
const { GITHUB_EVENT_BEFORE, GITHUB_SHA } = process.env;
const zero = /^0*$/;
if (GITHUB_EVENT_BEFORE && !zero.test(GITHUB_EVENT_BEFORE)) {
const d = git(['diff', '--name-only', GITHUB_EVENT_BEFORE, GITHUB_SHA || 'HEAD']);
if (d !== null) return d ? d.split('\n') : [];
}
const d = git(['diff', '--name-only', 'HEAD~1', 'HEAD']);
return d ? d.split('\n') : null;
};
const files = changedFiles();
if (files === null) {
console.log('IndexNow: kan ikke bestemme hvad der ændrede sig (shallow clone?) — springer over.');
process.exit(0);
}
const valid = sitemapUrls();
const urls = [...new Set(files.map(routeForFile).filter(Boolean).map((r) => ORIGIN + r))].filter((u) =>
valid.has(u),
);
// Layout/CSS/config-ændringer rammer hver side, men det er ikke en indholdsændring
// værd at pinge om. Kun ruter med eget ændret kildefil ryger afsted.
if (!urls.length) {
console.log('IndexNow: ingen sider med ændret indhold i dette push — springer over.');
process.exit(0);
}
const body = {
host: HOST,
key: KEY,
keyLocation: `${ORIGIN}/${KEY}.txt`,
urlList: urls,
};
console.log(`IndexNow: indsender ${urls.length} URL(er):`);
for (const u of urls) console.log(' ', u);
// `node scripts/indexnow.mjs --dry-run` viser hvad der ville blive sendt.
if (process.argv.includes('--dry-run')) {
console.log('IndexNow: --dry-run, sender ikke.');
process.exit(0);
}
const res = await fetch('https://api.indexnow.org/indexnow', {
method: 'POST',
headers: { 'Content-Type': 'application/json; charset=utf-8' },
body: JSON.stringify(body),
});
// 200 = modtaget, 202 = modtaget men nøglen valideres stadig. Begge er fine.
console.log(`IndexNow: HTTP ${res.status} ${res.statusText}`);
if (![200, 202].includes(res.status)) {
console.log(await res.text().catch(() => ''));
console.log('IndexNow: afvist — men deployet er allerede ude, så vi fejler ikke buildet.');
}