Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Binary file added public/apple-touch-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/favicon-32x32.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/favicon.ico
Binary file not shown.
Binary file added public/og-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions public/site.webmanifest
Original file line number Diff line number Diff line change
@@ -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"
}
]
}
47 changes: 47 additions & 0 deletions scripts/generate-og-assets.mjs
Original file line number Diff line number Diff line change
@@ -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(
`<svg xmlns="http://www.w3.org/2000/svg" width="${OG_W}" height="${OG_H}">` +
`<rect width="100%" height="100%" fill="#0a0b0d"/>` +
`<rect x="${(OG_W - LOGO_PX) / 2}" y="${(OG_H - LOGO_PX) / 2}" width="${LOGO_PX}" height="${LOGO_PX}" rx="${LOGO_PX * 0.21}" fill="#0a0b0d" stroke="#1f2125" stroke-width="2"/>` +
`<image href="data:image/svg+xml;base64,${logoB64}" x="${(OG_W - LOGO_PX) / 2}" y="${(OG_H - LOGO_PX) / 2}" width="${LOGO_PX}" height="${LOGO_PX}"/>` +
`</svg>`,
);
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",
);
29 changes: 27 additions & 2 deletions src/layouts/BaseLayout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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,
});
---
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/svg+xml" href={withBase('/favicon.svg')} />
<title>{title}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonical} />
<link rel="icon" type="image/svg+xml" href={withBase('/favicon.svg')} />
<link rel="icon" type="image/x-icon" href={withBase('/favicon.ico')} sizes="any" />
<link rel="icon" type="image/png" sizes="32x32" href={withBase('/favicon-32x32.png')} />
<link rel="apple-touch-icon" sizes="180x180" href={withBase('/apple-touch-icon.png')} />
<link rel="manifest" href={withBase('/site.webmanifest')} />
<meta name="theme-color" content="#0a0b0d" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content={SITE.name} />
<meta property="og:locale" content="en_US" />
<meta property="og:url" content={canonical} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta name="theme-color" content="#0a0b0d" />
<meta property="og:image" content={socialImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content={`${SITE.name} logo`} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content="@fastifyjs" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={socialImage} />
<script type="application/ld+json" set:html={jsonLd} />
<script is:inline>
(() => {
try {
Expand Down
Loading