Skip to content

Serve /favicon.ico instead of a 404 - #14

Open
cfboy wants to merge 1 commit into
mainfrom
chore/favicon-ico
Open

Serve /favicon.ico instead of a 404#14
cfboy wants to merge 1 commit into
mainfrom
chore/favicon-ico

Conversation

@cfboy

@cfboy cfboy commented Aug 15, 2026

Copy link
Copy Markdown
Owner

Follow-up to the SERP check: /favicon.ico returned 404 on production.

/favicon.ico           404 text/html
/favicon.svg           200 image/svg+xml
/apple-touch-icon.png  200 image/png

Nothing was actually broken — the <link rel="icon"> tag is well formed, robots.txt
blocks nothing, and Google reads SVG favicons. But /favicon.ico is the path
crawlers and older browsers request by convention before parsing <head>, so it
is worth not being a 404.

To be clear about scope: this will not refresh the icon Google is currently
showing.
That one is a cached copy of the pre-930c45a logo (still has the "titi"
text above the A). Googlebot-Favicon runs on its own schedule, measured in weeks,
and there is no way to force it. This change is housekeeping, not a fix for that.

How it was generated

The vector stays the source of truth. favicon.svg → rasterized to 256px with
sips → one frame per size at 16/32/48 (48 is what Google asks for; 16/32 keep
browser tabs crisp).

The part worth reviewing

Only the alpha channel is resampled. Resizing the RGBA image directly makes
Pillow premultiply — provably, since fully transparent pixels I had set to
(249,152,152,0) came back as (0,0,0,0) — and undoing that premultiplication
leaves the antialiased rim lighter than the fill:

approach colour of visible pixels
resize RGBA 26 distinct, (248,151,151)(255,169,169)
resample alpha only 1: (249,152,152)

On a mark this small that drift reads as a pale halo. Building each frame from flat
brand colour plus a resampled alpha fixes it, and cut the file from 4600 to 2649
bytes as a side effect.

Verification

  • Every visible pixel is exactly #f99898 at all three sizes
  • ICO binary parses: type=1, 3 frames, 32bpp, sane offsets
  • dist/favicon.ico byte-identical to public/favicon.ico; served 200 image/x-icon
  • The icon tag is present on all 14 prerendered routes
  • Build clean, 14 routes, sitemap unchanged
  • Eyeballed at 16/32/48 and through Google's circular crop — the crop still does not
    clip the A's legs, which is the constraint documented in favicon.svg

🤖 Generated with Claude Code

Crawlers and older browsers request /favicon.ico by path convention before
reading the icon tags in <head>, and that path returned 404. Nothing was
broken by it — the SVG tag is well formed, robots.txt blocks nothing, and
Google reads SVG favicons — but the fallback every other client reaches for
first was missing.

Generated from favicon.svg so the vector stays the source of truth: rasterized
to 256px, then one frame per size at 16/32/48 (48 is what Google asks for,
16/32 keep browser tabs crisp).

Only the alpha channel is resampled. Resizing the RGBA image directly makes
Pillow premultiply, and undoing that leaves the antialiased rim lighter than
the fill — measured up to (255,169,169) against a flat #f99898, a visible pale
halo on a mark this small. Building each frame from flat brand colour plus a
resampled alpha keeps every visible pixel exactly #f99898, and cut the file
from 4600 to 2649 bytes as a side effect.

The .ico is declared first and without a type so anything that understands SVG
still picks the vector.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant