perf(cms-client+ssr): O(1)-Slug-Lookup, Sparse-Pages, höhere TTLs, Cache-Control, Parallel-Resolver
Deploy / verify (push) Successful in 58s
Deploy / deploy (push) Successful in 1m12s

- getPageBySlug: neuer Filter-Lookup-Pfad (?slug=X&_fields=_slug) als
  primärer Fallback — O(1) statt komplette Page-Liste laden. Vollständige
  Liste nur noch als Last-Resort-Safety-Net. Skaliert egal wieviele Pages.
- getPages: Sparse-Projektion via _fields (8 Felder statt Voll-Resolve-
  Tree) → ~90 % kleinere Payload pro Call.
- CACHE_TTL hoch (Webhooks purgen eh): page/page_config/navigation/footer/
  fullwidth_banner/translation/openapi → 1h; tag/text_fragment → 30min;
  post → 10min. Spart Cold-Loads + CMS-Roundtrips.
- [...slug]/+page.server.ts: Cache-Control public, s-maxage=60,
  stale-while-revalidate=600 für öffentliche Pages (Caddy/Browser cachen).
  Preview-Branch bleibt private/no-store.
- Resolver parallelisiert: Calendar/Deadline/StellingnahmeGenerator
  starten sofort parallel zu PostOverview, nicht erst danach.
- Icon-Subset um clock-remove-outline ergänzt (Frist-abgelaufen-Banner).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-05-30 00:09:26 +02:00
parent f05ee6b9d7
commit c58f418c1a
3 changed files with 87 additions and 20 deletions
+18 -5
View File
@@ -38,6 +38,14 @@ export const load: PageServerLoad = async ({ params, locals, fetch, url, setHead
'cache-control': 'private, no-store, max-age=0',
'x-robots-tag': 'noindex, nofollow',
});
} else {
// Public Pages: Caddy/CDN + Browser cachen 60s, stale-while-revalidate
// erlaubt 10min Sofort-Lieferung während im Hintergrund neu geholt wird.
// CMS-Mutationen lösen Webhook → Frontend-Content-Cache-Purge aus, der
// Edge-Cache läuft per TTL aus (kurze Inkonsistenz akzeptabel).
setHeaders({
'cache-control': 'public, s-maxage=60, stale-while-revalidate=600',
});
}
const page = await getPageBySlug(slug, {
@@ -58,15 +66,20 @@ export const load: PageServerLoad = async ({ params, locals, fetch, url, setHead
// Tree — vor den Block-Resolvern erledigen, die danach gegen mutierte
// Blocks arbeiten.
await resolveContentImages(page);
// PostOverview liefert tagsMap, den SearchableText braucht. Calendar +
// DeadlineBanner sind unabhängig → parallel mit SearchableText laufen.
const tagsMap = await resolvePostOverviewBlocks(page);
await Promise.all([
resolveSearchableTextBlocks(page, tagsMap),
// PostOverview→SearchableText hat Daten-Abhängigkeit (tagsMap). Calendar,
// DeadlineBanner und StellingnahmeGenerator sind unabhängig — sofort
// parallel starten, nicht erst nach PostOverview warten.
const postOverviewPromise = resolvePostOverviewBlocks(page);
const independentResolvers = Promise.all([
resolveCalendarBlocks(page),
resolveDeadlineBannerBlocks(page),
resolveStellingnahmeGeneratorBlocks(page),
]);
const tagsMap = await postOverviewPromise;
await Promise.all([
resolveSearchableTextBlocks(page, tagsMap),
independentResolvers,
]);
sanitizeBlocks(page);
// Top-Bild: bevorzugt neues inline-Feld `topImage`, sonst Legacy via