From 37a6ce64ac7f9670f6d6004df44234f623d3bd89 Mon Sep 17 00:00:00 2001 From: Matteo Pietro Dazzi Date: Wed, 19 Aug 2026 09:18:32 +0200 Subject: [PATCH] fix(layout): display Fastify logo on social share previews MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Crawlers fell back to scraping a random inline icon from the page because BaseLayout only declared og:title and og:description. Add a proper 1200x630 OG card backed by the existing favicon logo, plus the favicon variants and webmanifest the audit (Google, X, LinkedIn, WhatsApp, Slack, iOS home screen) expect. - public/og-image.png (1200x630, logo on dark background) — declared as og:image, twitter:image and in the webmanifest - public/favicon.ico (32x32, real ICO container), favicon-32x32.png, apple-touch-icon.png (180x180) — cross-platform favicon fallbacks - public/site.webmanifest — PWA / 'Add to Home Screen' metadata - BaseLayout: og:url, og:image:alt, og:site_name, og:locale, twitter:card=summary_large_image, twitter:site=@fastifyjs, the new link tags, and a minimal WebSite JSON-LD block. Assets are committed; scripts/generate-og-assets.mjs regenerates them from public/favicon.svg via sharp (already a transitive dep). --- package.json | 3 ++- public/apple-touch-icon.png | Bin 0 -> 4917 bytes public/favicon-32x32.png | Bin 0 -> 809 bytes public/favicon.ico | Bin 0 -> 831 bytes public/og-image.png | Bin 0 -> 28985 bytes public/site.webmanifest | 19 +++++++++++++ scripts/generate-og-assets.mjs | 47 +++++++++++++++++++++++++++++++++ src/layouts/BaseLayout.astro | 29 ++++++++++++++++++-- 8 files changed, 95 insertions(+), 3 deletions(-) create mode 100644 public/apple-touch-icon.png create mode 100644 public/favicon-32x32.png create mode 100644 public/favicon.ico create mode 100644 public/og-image.png create mode 100644 public/site.webmanifest create mode 100644 scripts/generate-og-assets.mjs diff --git a/package.json b/package.json index 71fbd70c..16990835 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "lint": "biome check", "lint:fix": "biome check --write", "check": "astro check", - "build:data": "node scripts/postinstall.mjs" + "build:data": "node scripts/postinstall.mjs", + "generate:og-assets": "node scripts/generate-og-assets.mjs" }, "dependencies": { "@astrojs/mdx": "^7.0.5", diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8877c801e3fa430ff6e04e213db77d49344a554a GIT binary patch literal 4917 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 literal 0 HcmV?d00001 diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..e014abb213f0352e8fc49f3d149d64d3031cc3a5 GIT binary patch literal 809 zcmV+^1J?YBP)2zXjd>n&= zgXr$=#_;ekyk0MOysvM7)mn%C{$KKpM{TWHy8{pjc@va+9OBXH^R70k`eiQ+3OD*`w!J`qz>f5h3w#)j~=udiP!u-$u?Yf_s6 z1%*XeU0s!hIy-;BVtI$PwKaJ*GBSdk+&nmYdhoTqU3&k`;XrZmvt7O4l|V|$O?+%@ z3Xqq3>+yI3&R)NHi+lGUATujlj2WK_fA6DD$G{X389IUZgd{94FH0b|+l}Pp8>p&& zB|P;7A4Pw;hlYmm@KHLNZ8o9Kz`%eu1()$jH=2 zfU*c7YiVg2@(T)gTyJapvIB5&aSpz~7QufI}vblba`e<+E?Me-gcL5MWIfsMLhWBT>2l zYwiJ}b1M9^$ly;hYWPQKvvCAiqrw=AF=#PamwO`L)eMloZ2NLr9wsIDa{40JhV0uD nn|O2RJRNRPldwi^xj*J#`bg%{G_urD00000NkvXXu0mjfo1cL3 literal 0 HcmV?d00001 diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c8155b4c2df9aa5cf1530d7b18276eb49d5ada66 GIT binary patch literal 831 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 literal 0 HcmV?d00001 diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 00000000..4a8ead31 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "Fastify", + "short_name": "Fastify", + "description": "Fast and low overhead web framework, for Node.js", + "start_url": "/", + "display": "standalone", + "background_color": "#0a0b0d", + "theme_color": "#0a0b0d", + "icons": [ + { "src": "/favicon-32x32.png", "sizes": "32x32", "type": "image/png" }, + { "src": "/apple-touch-icon.png", "sizes": "180x180", "type": "image/png" }, + { + "src": "/og-image.png", + "sizes": "1200x630", + "type": "image/png", + "purpose": "any" + } + ] +} diff --git a/scripts/generate-og-assets.mjs b/scripts/generate-og-assets.mjs new file mode 100644 index 00000000..0227fa26 --- /dev/null +++ b/scripts/generate-og-assets.mjs @@ -0,0 +1,47 @@ +#!/usr/bin/env node +// @ts-nocheck +import { readFile, writeFile } from "node:fs/promises"; +import sharp from "sharp"; + +const ROOT = new URL("..", import.meta.url).pathname; +const SRC = `${ROOT}public/favicon.svg`; +const OUT = `${ROOT}public`; + +const svg = await readFile(SRC); + +// 1200x630 OG card: dark bg, logo centered inside a rounded inset square. +const OG_W = 1200; +const OG_H = 630; +const LOGO_PX = 360; +const logoB64 = svg.toString("base64"); +const ogSvg = Buffer.from( + `` + + `` + + `` + + `` + + ``, +); +await sharp(ogSvg).png().toFile(`${OUT}/og-image.png`); + +await sharp(svg).resize(180, 180).png().toFile(`${OUT}/apple-touch-icon.png`); + +await sharp(svg).resize(32, 32).png().toFile(`${OUT}/favicon-32x32.png`); + +// Wrap a 32x32 PNG in a minimal ICO container (1 image, type=icon) so Safari +// and IE accept it as a real .ico instead of guessing by filename. +const png = await sharp(svg).resize(32, 32).png().toBuffer(); +const header = Buffer.alloc(6 + 16); +header.writeUInt16LE(0, 0); +header.writeUInt16LE(1, 2); +header.writeUInt16LE(1, 4); +header.writeUInt8(32, 6); +header.writeUInt8(32, 7); +header.writeUInt16LE(1, 10); +header.writeUInt16LE(32, 12); +header.writeUInt32LE(png.length, 14); +header.writeUInt32LE(6 + 16, 18); +await writeFile(`${OUT}/favicon.ico`, Buffer.concat([header, png])); + +console.log( + "wrote: og-image.png, apple-touch-icon.png, favicon-32x32.png, favicon.ico", +); diff --git a/src/layouts/BaseLayout.astro b/src/layouts/BaseLayout.astro index 088d7fc2..8cbf5da1 100644 --- a/src/layouts/BaseLayout.astro +++ b/src/layouts/BaseLayout.astro @@ -18,20 +18,45 @@ const { description = SITE.description, } = Astro.props; const canonical = new URL(Astro.url.pathname, Astro.site ?? SITE.url).href; +const socialImage = new URL(withBase("/og-image.png"), Astro.site ?? SITE.url).href; +const jsonLd = JSON.stringify({ + "@context": "https://schema.org", + "@type": "WebSite", + name: SITE.name, + url: SITE.url, + description: SITE.description, +}); --- - {title} + + + + + + + + + - + + + + + + + + + +