Build & Deploy / deploy (push) Successful in 2m19s
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
170 lines
5.6 KiB
JavaScript
170 lines
5.6 KiB
JavaScript
import { defineConfig } from 'astro/config';
|
|
import sitemap from '@astrojs/sitemap';
|
|
import AstroPWA from '@vite-pwa/astro';
|
|
import tailwindcss from '@tailwindcss/vite';
|
|
import { execFileSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
|
|
// ── lastmod til sitemap ────────────────────────────────────────────────────────
|
|
// Google bruger kun lastmod hvis den er troværdig, så vi gætter aldrig: statiske
|
|
// sider får datoen for deres SIDSTE COMMIT, blogindlæg får updated/date fra
|
|
// frontmatter, og alt vi ikke kan datere præcist får slet ingen lastmod.
|
|
// NB: kræver fuld git-historik — deploy-workflowen bruger fetch-depth: 0.
|
|
// Er historikken shallow (eller mangler .git), falder vi stille tilbage til
|
|
// "ingen lastmod" i stedet for at udgive en forkert dato.
|
|
|
|
const gitLastModified = (file) => {
|
|
try {
|
|
const out = execFileSync('git', ['log', '-1', '--format=%cI', '--', file], {
|
|
cwd: ROOT,
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
}).trim();
|
|
return out || null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
// Shallow clone => hver fil ser ud til at være ændret i den ene commit vi har.
|
|
// Så er datoerne værdiløse, og vi dropper lastmod helt.
|
|
const gitHistoryIsUsable = (() => {
|
|
try {
|
|
const n = execFileSync('git', ['rev-list', '--count', 'HEAD'], {
|
|
cwd: ROOT,
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
}).trim();
|
|
return Number(n) > 1;
|
|
} catch {
|
|
return false;
|
|
}
|
|
})();
|
|
|
|
const walk = (dir) =>
|
|
fs.existsSync(dir)
|
|
? fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
|
|
const p = path.join(dir, e.name);
|
|
return e.isDirectory() ? walk(p) : [p];
|
|
})
|
|
: [];
|
|
|
|
// src/pages/foo.astro -> /foo/ ; src/pages/index.astro -> / ; [dynamiske] springes over
|
|
const routeForPage = (file) => {
|
|
const rel = path.relative(path.join(ROOT, 'src/pages'), file).replace(/\\/g, '/');
|
|
if (rel.includes('[') || rel.startsWith('404.')) return null;
|
|
if (!rel.endsWith('.astro')) return null;
|
|
const noExt = rel.slice(0, -'.astro'.length);
|
|
if (noExt === 'index') return '/';
|
|
return `/${noExt.replace(/\/index$/, '')}/`;
|
|
};
|
|
|
|
// Minimal frontmatter-læsning — vi skal kun bruge date/updated/tags/draft.
|
|
const readFrontmatter = (file) => {
|
|
const src = fs.readFileSync(file, 'utf8');
|
|
const m = src.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
if (!m) return {};
|
|
const fm = {};
|
|
for (const line of m[1].split(/\r?\n/)) {
|
|
const kv = line.match(/^(\w+):\s*(.*)$/);
|
|
if (!kv) continue;
|
|
const [, k, rawv] = kv;
|
|
const v = rawv.trim().replace(/^['"]|['"]$/g, '');
|
|
if (k === 'tags') {
|
|
const inline = v.match(/^\[(.*)\]$/);
|
|
fm.tags = inline
|
|
? inline[1].split(',').map((t) => t.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean)
|
|
: [];
|
|
} else {
|
|
fm[k] = v;
|
|
}
|
|
}
|
|
return fm;
|
|
};
|
|
|
|
const iso = (v) => {
|
|
const d = new Date(v);
|
|
return Number.isNaN(d.getTime()) ? null : d.toISOString();
|
|
};
|
|
const newest = (dates) => dates.filter(Boolean).sort().at(-1) ?? null;
|
|
|
|
const buildLastmodMap = () => {
|
|
const map = new Map();
|
|
if (!gitHistoryIsUsable) return map;
|
|
|
|
for (const file of walk(path.join(ROOT, 'src/pages'))) {
|
|
const route = routeForPage(file);
|
|
if (!route) continue;
|
|
const d = gitLastModified(file);
|
|
if (d) map.set(route, d);
|
|
}
|
|
|
|
// Blogindlæg: frontmatter er sandheden om hvornår indholdet sidst ændrede sig.
|
|
const postDates = [];
|
|
const tagDates = new Map();
|
|
for (const file of walk(path.join(ROOT, 'src/content/blog'))) {
|
|
if (!file.endsWith('.md')) continue;
|
|
const fm = readFrontmatter(file);
|
|
if (fm.draft === 'true') continue;
|
|
const d = iso(fm.updated || fm.date);
|
|
if (!d) continue;
|
|
const slug = path.basename(file, '.md');
|
|
map.set(`/blog/${slug}/`, d);
|
|
postDates.push(d);
|
|
for (const t of fm.tags ?? []) {
|
|
const key = t.toLowerCase();
|
|
tagDates.set(key, newest([tagDates.get(key), d]));
|
|
}
|
|
}
|
|
|
|
// Oversigtssider er lige så friske som det nyeste de viser.
|
|
const latest = newest(postDates);
|
|
if (latest) {
|
|
map.set('/blog/', latest);
|
|
map.set('/tags/', latest);
|
|
for (const [tag, d] of tagDates) map.set(`/tags/${tag}/`, d);
|
|
}
|
|
return map;
|
|
};
|
|
|
|
const lastmodByRoute = buildLastmodMap();
|
|
|
|
export default defineConfig({
|
|
site: 'https://asger.xn--weirse-fya.dk',
|
|
trailingSlash: 'ignore',
|
|
integrations: [
|
|
sitemap({
|
|
changefreq: 'weekly',
|
|
priority: 0.7,
|
|
serialize(item) {
|
|
const pathname = new URL(item.url).pathname;
|
|
if (pathname === '/') item.priority = 1.0;
|
|
const lastmod = lastmodByRoute.get(pathname);
|
|
if (lastmod) item.lastmod = lastmod;
|
|
return item;
|
|
},
|
|
}),
|
|
// Service worker → offline support + installable PWA. autoUpdate + outdated-cache
|
|
// cleanup so a redeploy never strands a visitor on a stale version. Reuses the
|
|
// existing public/site.webmanifest (manifest: false = don't generate another).
|
|
AstroPWA({
|
|
registerType: 'autoUpdate',
|
|
manifest: false,
|
|
includeManifestIcons: false,
|
|
workbox: {
|
|
globPatterns: ['**/*.{html,css,js,svg,png,woff2,xml,txt,webmanifest}'],
|
|
cleanupOutdatedCaches: true,
|
|
clientsClaim: true,
|
|
skipWaiting: true,
|
|
},
|
|
devOptions: { enabled: false },
|
|
}),
|
|
],
|
|
prefetch: { prefetchAll: true, defaultStrategy: 'viewport' },
|
|
vite: { plugins: [tailwindcss()] },
|
|
});
|