Google Sites Performance / Lighthouse audits of https://krillswarm.com
flagged a cluster of issues that depressed the SEO and best-practices
scores: the viewport meta tag disabled pinch-zoom, every page made a
404 request for a missing favicon-32x32.png, the PWA web manifest
declared icon dimensions that did not match the file it pointed at,
the <meta name="theme-color"> was hard-coded to #ffffff for both
light and dark modes, and the SMTP-email-alerts post had no
description: front-matter so its <meta> and og:description
fell back to a truncated body excerpt. On top of that, every page
fired GA4 page_view twice (once via analytics/google.html, once via
analytics/firebase.html) because both blocks loaded gtag.js with
the same measurement ID.
Each finding traced to a single line of template / config drift that slipped past the GH-Pages deploy because Pages does not run Lighthouse or schema validation:
_includes/head.html had content="width=device-width,
user-scalable=no initial-scale=1, ..." — both user-scalable=no
(Lighthouse a11y / best-practices fail) and a missing comma between
no and initial-scale=1 that made the second directive invalid._includes/favicons.html linked /assets/img/favicons/favicon-32x32.png
but the file was never committed (only the .ico, the 16x16 PNG, and
apple-touch are shipped). It also unconditionally emitted
<meta name="theme-color" content="#ffffff"> after the
prefers-color-scheme variants set in head.html, so the white value
always won.assets/img/favicons/site.webmanifest referenced mstile-150x150.png
twice with declared sizes 192x192 and 512x512; manifest validators
reject icon entries whose declared sizes do not match the actual
pixel dimensions._config.yml set analytics.google.id and analytics.firebase.measurement_id
to the same G-6G958DLSTQ. head.html iterates site.analytics and
includes every block whose id is non-empty, so both google.html
(defer) and firebase.html (async) loaded gtag with the same ID,
double-firing every page_view._layouts/home.html rendered the post-list <img> with no loading,
no decoding, and no explicit width/height, costing CLS and LCP
on the home grid below the hero._data/origin/cors.yml only preconnected to fonts.googleapis.com,
fonts.gstatic.com, and cdn.jsdelivr.net. The site also pulls
Font Awesome via kit.fontawesome.com (which then loads from
ka-f.fontawesome.com) and analytics from googletagmanager.com,
none of which had resource hints._posts/2026-04-03-smtp-email-alerts.md had no description: field
in its YAML front-matter, leaving jekyll-seo-tag to derive the
meta-description from a truncated post body.Patches are minimal and contained to docs/:
head.html — removed user-scalable=no and the malformed comma.favicons.html — dropped the broken favicon-32x32.png link, dropped
the unconditional theme-color="#ffffff" so the
prefers-color-scheme variants in head.html take effect, and
re-themed msapplication-TileColor to #1b1b1e to match the dark
theme.site.webmanifest — declared truthful icon sizes (150x150 for the
mstile, 180x180 for the apple-touch), and aligned theme_color /
background_color with the dark theme._config.yml — emptied analytics.google.id (the GA4 measurement
is already covered by analytics.firebase.measurement_id); added a
comment block explaining the double-fire trap so the next editor
does not re-introduce it.home.html — added loading="lazy" decoding="async" width="1200" height="630"
to the post-list <img>.cors.yml — added preconnect + dns-prefetch for
kit.fontawesome.com, ka-f.fontawesome.com, and googletagmanager.com.2026-04-03-smtp-email-alerts.md — added a one-line description:.A regression-guard DocsSeoTest lives in server/src/jvmTest/ and
asserts each of the bad shapes (literal user-scalable=no, the
missing PNG link, the white theme-color override, the wrong manifest
sizes, the duplicate GA4 id, and posts missing description:) is gone.
The PR Verify workflow runs :server:jvmTest, so a regression on any
of these fails CI before GH Pages deploys.
DocsSeoTest is the durable guard. Each assertFalse carries the
exact substring that was wrong, so future edits that re-introduce
the bad pattern fail loudly with a message that names the bug.head.html analytics loop iterates every
configured platform whose id is non-empty. Two platforms with the
same GA4 measurement ID will always double-fire — keep at most one
block populated for any given gtag stream..github/workflows/Release Web.yml only does bundle exec jekyll build.