diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml
index cee780e..f9bbf79 100644
--- a/.gitea/workflows/deploy.yml
+++ b/.gitea/workflows/deploy.yml
@@ -12,9 +12,7 @@ jobs:
- uses: actions/checkout@v4
with:
submodules: recursive
- # Fuld historik: astro.config.mjs udleder sitemap-lastmod fra hver sides
- # sidste commit, og IndexNow-steppet differ pushet. Begge er værdiløse
- # med den default shallow clone (fetch-depth: 1).
+ # Fuld historik til sitemap-lastmod og IndexNow
fetch-depth: 0
- name: Setup Node
@@ -22,10 +20,7 @@ jobs:
with:
node-version: 22
- # The runner forces omit of optional deps, dropping @img/sharp-linux-x64
- # (sharp's platform binary) -> astro:assets MissingSharp. Clear the omit
- # env and force-install the linux/glibc/x64 sharp binary per sharp's
- # official cross-platform install, then verify it loads.
+ # Runneren udelader optional deps, så sharp-binæren installeres eksplicit
- name: Install dependencies
env:
npm_config_omit: ""
@@ -35,7 +30,6 @@ jobs:
npm install --include=optional --os=linux --libc=glibc --cpu=x64 sharp
node -e "console.log('sharp', require('sharp').versions.sharp)"
- # Intet indlæg bygges uden godkendelse; se scripts/verify-approvals.mjs.
- name: Verify blog approvals
env:
PUBLISH_KEY: ${{ secrets.PUBLISH_KEY }}
@@ -44,11 +38,7 @@ jobs:
- name: Build site
run: npm run build
- # tar over SSH (the runner image has no rsync). Build is unpacked into
- # a .new dir on the web host and atomically swapped in, so the live web
- # root is never left half-written. Host and path are repository secrets,
- # not variables: run logs of a public repo are public, and Gitea prints
- # variables in them but masks secrets.
+ # tar over SSH med atomisk swap (runner-imaget har ikke rsync)
- name: Deploy dist/ over SSH
env:
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
@@ -72,9 +62,6 @@ jobs:
&& mv $DEPLOY_PATH.new $DEPLOY_PATH \
&& rm -rf $DEPLOY_PATH.old"
- # Fortæl Bing/Yandex m.fl. med det samme hvilke sider der ændrede sig.
- # Kører EFTER deploy, så nøglefilen og siderne er live når de kommer og tjekker.
- # Google understøtter ikke IndexNow. Må aldrig vælte et ellers vellykket deploy.
- name: Ping IndexNow
continue-on-error: true
env:
diff --git a/astro.config.mjs b/astro.config.mjs
index 0ee55e1..105fb99 100644
--- a/astro.config.mjs
+++ b/astro.config.mjs
@@ -9,13 +9,8 @@ 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.
+// lastmod til sitemap: sidste commit for statiske sider, frontmatter for blogindlæg.
+// Kræver fuld git-historik (fetch-depth: 0); ellers ingen lastmod.
const gitLastModified = (file) => {
try {
@@ -30,8 +25,6 @@ const gitLastModified = (file) => {
}
};
-// 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'], {
@@ -145,14 +138,8 @@ export default defineConfig({
sitemap({
changefreq: 'weekly',
priority: 0.7,
- // /tarballz/ er ikke en Astro-side (genereres på webhosten), men oversigten må
- // gerne indekseres; selve pakkefilerne er noindex der.
customPages: ['https://asger.xn--weirse-fya.dk/tarballz/'],
- // Tag-siderne er noindex (tynde lister), så de hører heller ikke i sitemap'et.
- // /peppol er kun en omdirigering til /nemhandel og skal ikke indekseres.
- // /cookies, /status og /chat er hjælpesider uden søgeværdi; de er noindex
- // og står derfor heller ikke i sitemap'et (ellers melder Search Console
- // "indsendt URL markeret som noindex").
+ // noindex-sider og omdirigeringer
filter: (page) => {
const p = new URL(page).pathname.replace(/\/$/, '');
return !p.startsWith('/tags') && !p.startsWith('/peppol')
@@ -163,18 +150,11 @@ export default defineConfig({
if (pathname === '/') item.priority = 1.0;
const lastmod = lastmodByRoute.get(pathname);
if (lastmod) item.lastmod = lastmod;
- // Samme kanoniske form som og de interne links:
- // uden afsluttende skråstreg. Slås lastmod op FØR strippet, fordi
- // lastmodByRoute er nøglet på Astros egne ruter (som har skråstregen).
- // Undtagen /tarballz/: den er ikke en Astro-side, og webhosten serverer
- // den kun med skråstreg (uden skråstreg giver den en 301).
+ // Kanonisk form uden afsluttende skråstreg (undtagen /tarballz/)
if (pathname !== '/' && pathname !== '/tarballz/') item.url = item.url.replace(/\/$/, '');
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,
@@ -184,10 +164,7 @@ export default defineConfig({
cleanupOutdatedCaches: true,
clientsClaim: true,
skipWaiting: true,
- // No SPA fallback. The default answers EVERY navigation it has not
- // precached with the cached front page, so /tarballz/ (served by nginx
- // outside the Astro build) and unknown URLs showed the home page instead
- // of the real page or the 404. Every Astro page is precached anyway.
+ // Ingen fallback til forsiden, så /tarballz/ og 404 virker
navigateFallback: null,
},
devOptions: { enabled: false },
diff --git a/scripts/indexnow.mjs b/scripts/indexnow.mjs
index 01e8753..f2424c0 100644
--- a/scripts/indexnow.mjs
+++ b/scripts/indexnow.mjs
@@ -1,11 +1,5 @@
-// 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.
+// Pinger IndexNow med de sider der er ændret i dette push.
+// Kør efter `astro build` (bruger dist/sitemap-0.xml).
import { execFileSync } from 'node:child_process';
import fs from 'node:fs';
@@ -62,8 +56,6 @@ const urls = [...new Set(files.map(routeForFile).filter(Boolean).map((r) => ORIG
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);
@@ -91,7 +83,6 @@ const res = await fetch('https://api.indexnow.org/indexnow', {
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(() => ''));
diff --git a/scripts/verify-approvals.mjs b/scripts/verify-approvals.mjs
index 8cbca88..c04f4c3 100644
--- a/scripts/verify-approvals.mjs
+++ b/scripts/verify-approvals.mjs
@@ -1,14 +1,5 @@
-// Spærre før build: intet blogindlæg går live uden at være godkendt.
-//
-// Hvert indlæg i src/content/blog skal bære et `godkendt:`-stempel i frontmatter.
-// Stemplet er en HMAC-SHA256 over filens sti + indhold (uden selve stempel-linjen)
-// + sha256 af hvert billede i indlæggets mappe (src/content/blog//),
-// lavet med en nøgle som kun godkendelsestjenesten og denne workflow kender. Et
-// indlæg der er lagt direkte i repoet, eller ændret efter godkendelse, har derfor
-// intet eller et forkert stempel, og så stopper buildet før noget bliver deployet.
-//
-// Udkast hører ikke hjemme her: repoet er offentligt, så `draft: true` ville stadig
-// udgive teksten via git. Udkast ligger i et privat repo indtil de er godkendt.
+// Stopper buildet hvis et blogindlæg mangler et gyldigt `godkendt:`-stempel
+// (HMAC-SHA256 over sti, indhold og billeder).
//
// PUBLISH_KEY= node scripts/verify-approvals.mjs
import crypto from 'node:crypto';
@@ -21,8 +12,7 @@ if (!key) {
process.exit(1);
}
-// Skal matche godkendelsestjenesten byte for byte. Et indlæg uden billeder har
-// ingen @asset-linjer, så dets stempel er det samme som før billeder fandtes.
+// Skal matche godkendelsestjenesten byte for byte.
const canonical = (relPath, text, assets = []) => {
const lines = text.replace(/\r\n/g, '\n').split('\n');
if (lines[0] === '---') {
diff --git a/src/components/Chat.astro b/src/components/Chat.astro
index 04a8ba5..09f2848 100644
--- a/src/components/Chat.astro
+++ b/src/components/Chat.astro
@@ -32,7 +32,6 @@ const t = lang === 'en' ? {
function esc(s) {
return s.replace(/&/g, '&').replace(//g, '>');
}
- // Escape FØRST, linkificér så: rene URL'er og e-mails bliver klikbare, intet HTML slipper igennem.
function linkify(text) {
var html = esc(text);
html = html.replace(/\b(https?:\/\/[^\s<]+[^\s<.,!?:;)])/g,
@@ -41,8 +40,6 @@ const t = lang === 'en' ? {
'$1');
return html;
}
- // Track spørgsmålet i Matomo FRA browseren, så det knyttes til den besøgendes
- // egen visitor-profil (cookie/visitor-ID, IP, browser) i stedet for en server-IP.
function track(action, q) {
try { (window._paq = window._paq || []).push(['trackEvent', 'Chat', action, q]); } catch (_) {}
}
diff --git a/src/components/Head.astro b/src/components/Head.astro
index e07dc59..f525299 100644
--- a/src/components/Head.astro
+++ b/src/components/Head.astro
@@ -11,8 +11,7 @@ interface Props {
modifiedTime?: string;
tags?: string[];
jsonLd?: unknown[];
- // Tynde oversigtssider (fx tags med 1-2 indlæg) skal ikke i Googles indeks,
- // men links fra dem skal stadig følges.
+ // noindex, follow
noindex?: boolean;
// Sidens sprog (standard: dansk) og dens oversættelser, som hreflang-links.
lang?: 'da' | 'en';
@@ -28,9 +27,7 @@ const xDefault = hreflang.find((a) => a.lang === 'da');
const pageTitle = title ? `${title} · ${SITE.name}` : `${SITE.title} · Datalog & Technical Solution Architect`;
const desc = description ?? SITE.description;
-// Kanonisk form er UDEN afsluttende skråstreg. Origin serverer begge former med
-// 200, og de interne links peger alle på no-slash, så canonical og sitemap skal
-// gøre det samme, ellers opdager Google to adskilte sæt af de samme sider.
+// Kanonisk form er uden afsluttende skråstreg, som i sitemap og interne links.
const canonicalPath = Astro.url.pathname.replace(/\/$/, '') || '/';
const canonical = new URL(canonicalPath, SITE.url).href;
const ogImage = new URL(image ?? '/og.png', SITE.url).href;
@@ -76,20 +73,15 @@ const graphs = [baseGraph, ...jsonLd];
-
{graphs.map((g) => )}
-
-
{!PREVIEW && }
@@ -76,7 +73,7 @@ const { title, description, image, type, publishedTime, modifiedTime, tags, json