feat(seo): image sitemap, Article schemas on apologetik pro + katechese, edge caching

Sitemap now declares the image:image namespace and emits an entry per recipe
photo (loc, title from recipe name, caption from alt text) — Google Image
Search can discover all recipe images directly instead of relying on crawl.

Pro arg pages get Article JSON-LD (headline, claim as description, layer as
articleSection, voice names as keywords, deduped voice cites as citations)
plus BreadcrumbList. Katechese/zehn-gebote gets inline Article + Breadcrumb
with a Thing reference to "Dekalog" and CreativeWork citation of P. Martin
Ramm FSSP's Glaubenskurs.

Static-content load functions now set Cache-Control:
public,max-age=300,s-maxage=3600,stale-while-revalidate=86400 — applied to
apologetik layout, contra index/arg, pro index/arg, and the new katechese
+page.ts files. Recipe detail uses s-maxage=1800. Picked HTTP caching over
SvelteKit prerender to avoid baking session=null into the navbar of routes
shared with the auth-aware faithLang layout.
This commit is contained in:
2026-05-02 22:05:50 +02:00
parent ecbd24d7a4
commit d59cc0a732
13 changed files with 166 additions and 15 deletions
+49 -1
View File
@@ -1,4 +1,4 @@
import type { Argument, Archetype } from '$lib/data/apologetik';
import type { Argument, Archetype, PosArgument, PosVoice } from '$lib/data/apologetik';
import type { FaithLang } from '$lib/js/faithI18n';
import { faithSlugFromLang, apologetikSlug } from '$lib/js/faithI18n';
@@ -66,3 +66,51 @@ export function generateContraQaJsonLd(
},
};
}
export interface ProArgArticleJsonLd {
'@context': 'https://schema.org';
'@type': 'Article';
headline: string;
description: string;
inLanguage: FaithLang;
url: string;
mainEntityOfPage: string;
author: { '@type': 'Person'; name: string; url: string };
publisher: { '@type': 'Person'; name: string; url: string };
articleSection: string;
keywords?: string;
citation?: Array<{ '@type': 'CreativeWork'; name: string }>;
}
/** Build an Article JSON-LD for a positive (pro) apologetik argument. */
export function generateProArgArticleJsonLd(
arg: PosArgument,
voices: Record<string, PosVoice>,
lang: FaithLang
): ProArgArticleJsonLd {
const faithSeg = faithSlugFromLang(lang);
const apolSeg = apologetikSlug(lang === 'la' ? 'en' : lang);
const url = `https://bocken.org/${faithSeg}/${apolSeg}/pro/${arg.id}`;
const allCites = Object.values(arg.voices).flatMap((v) => v.cites ?? []);
const uniqueCites = Array.from(new Set(allCites));
const voiceNames = Object.keys(arg.voices)
.map((id) => voices[id]?.name ?? id)
.join(', ');
const article: ProArgArticleJsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: arg.title,
description: arg.claim,
inLanguage: lang,
url,
mainEntityOfPage: url,
author: { '@type': 'Person', name: 'Alexander Bocken', url: 'https://bocken.org/' },
publisher: { '@type': 'Person', name: 'Alexander Bocken', url: 'https://bocken.org/' },
articleSection: arg.layer,
};
if (voiceNames) article.keywords = voiceNames;
if (uniqueCites.length) article.citation = uniqueCites.map((name) => ({ '@type': 'CreativeWork', name }));
return article;
}