perf: review-fixes (N+1, waterfalls, sanitize-cache, lazy strommix)
Deploy / verify (push) Successful in 56s
Deploy / deploy (push) Successful in 1m3s

- blog-utils.resolvePostOverviewBlocks: parallel getPostBySlug via
  Promise.all + .catch(null) instead of sequential await loop.
- blog-utils.resolveCalendarBlocks: same fix for calendar items.
- [...slug] page load: SearchableText/Calendar/DeadlineBanner now run
  parallel after tagsMap (was strict waterfall).
- sanitize-blocks: LRU cache keyed on raw html input — sanitize-html is
  ~ms/KB, the same content repeats across pages and cache hits.
- cms.getNormalizedTranslations: WeakMap-memoised the per-request
  Object.entries normalize loop on top of the existing 5-min bundle TTL.
- cms.cached: opt-in dev cache via DEV_CACHE_CMS=1 (default still off).
- rusty-image.ensureTransformedImage: was async-wrapping a sync URL
  builder; made sync, removed awaits at call sites — saves microtasks.
- BlockRenderer: lazy-load StrommixBlock (~220 KB) via dynamic import
  in the template — pages without the live-strommix widget no longer
  ship its bundle.

Also: enabled Caddy zstd+gzip on windwiderstand.de vhost (server-side
edit; verified `content-encoding: zstd` on response).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Peter Meier
2026-05-21 12:30:15 +02:00
parent c00eb26689
commit 81c82c33e6
9 changed files with 144 additions and 91 deletions
+35 -1
View File
@@ -95,7 +95,10 @@ async function cached<T>(
key: string,
fn: () => Promise<T>,
): Promise<T> {
if (import.meta.env.DEV) return fn();
// Dev: Cache aus by default (sofortige Reflektion neuer CMS-Daten).
// Mit DEV_CACHE_CMS=1 anschalten — sinnvoll wenn lokal gegen Prod-CMS
// entwickelt wird, sonst werden Netzwerk-Roundtrips bei jedem Request fällig.
if (import.meta.env.DEV && process.env.DEV_CACHE_CMS !== "1") return fn();
const hit = cache.get(key);
if (hit && hit.expires > Date.now()) return hit.value as T;
const pending = inflight.get(key) as Promise<T> | undefined;
@@ -847,6 +850,37 @@ export async function getTranslationBundleBySlug(
});
}
/**
* Normalised key→string map for `event.locals.translations`. Cached on the
* raw `strings` object identity so the per-request `Object.entries` loop runs
* once per bundle reload (every 5 min) instead of every request.
*/
const normalizedTranslations = new WeakMap<object, Record<string, string>>();
export async function getNormalizedTranslations(
slug: string,
options?: { locale?: string },
): Promise<Record<string, string>> {
const bundle = await getTranslationBundleBySlug(slug, options);
const strings = bundle?.strings;
if (!strings || typeof strings !== "object") return {};
const cached = normalizedTranslations.get(strings as object);
if (cached) return cached;
const out: Record<string, string> = {};
for (const [key, val] of Object.entries(strings as Record<string, unknown>)) {
const str =
typeof val === "string"
? val
: typeof val === "object" && val !== null && "value" in val
? (val as { value?: unknown }).value
: (val as { text?: unknown })?.text ??
(val as { label?: unknown })?.label;
if (typeof str === "string") out[key] = str;
}
normalizedTranslations.set(strings as object, out);
return out;
}
/** Kalender-Eintrag (calendar) aus OpenAPI-Spec. */
export type CalendarEntry = components["schemas"]["calendar"];