Add Open Graph image generation for all docs pages

Custom renderer with NASA-blue theme, Inter font, signal arc decoration.
Generates 1200x630 PNG per page at build time via astro-opengraph-images.
Head component injects og:image meta tag using getImagePath().
This commit is contained in:
Ryan Malloy 2026-02-21 09:28:45 -07:00
parent db14b85633
commit a06f5e8dc1
5 changed files with 161 additions and 2 deletions

View File

@ -3,11 +3,15 @@ import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import icon from 'astro-icon';
import rehypeMermaid from 'rehype-mermaid';
import opengraphImages from 'astro-opengraph-images';
import { render as ogRender } from './src/og-image.tsx';
import * as fs from 'node:fs';
// astro-opengraph-images installed but needs font setup:
// import astroOpenGraphImages, { presets } from 'astro-opengraph-images';
const interRegular = fs.readFileSync('node_modules/@fontsource/inter/files/inter-latin-400-normal.woff');
const interBold = fs.readFileSync('node_modules/@fontsource/inter/files/inter-latin-700-normal.woff');
export default defineConfig({
site: 'https://gr-apollo.l.warehack.ing',
integrations: [
icon(),
starlight({
@ -57,6 +61,25 @@ export default defineConfig({
SocialIcons: './src/components/SocialIcons.astro',
},
}),
opengraphImages({
options: {
fonts: [
{
name: 'Inter',
weight: 400,
style: 'normal',
data: interRegular,
},
{
name: 'Inter',
weight: 700,
style: 'normal',
data: interBold,
},
],
},
render: ogRender,
}),
],
markdown: {
rehypePlugins: [

14
docs/package-lock.json generated
View File

@ -16,6 +16,10 @@
"mermaid": "^11.12.3",
"rehype-mermaid": "^3.0.0",
"sharp": "^0.34.2"
},
"devDependencies": {
"@fontsource/inter": "^5.2.8",
"react": "^19.2.4"
}
},
"node_modules/@acemir/cssom": {
@ -986,6 +990,16 @@
"@expressive-code/core": "^0.41.6"
}
},
"node_modules/@fontsource/inter": {
"version": "5.2.8",
"resolved": "https://registry.npmjs.org/@fontsource/inter/-/inter-5.2.8.tgz",
"integrity": "sha512-P6r5WnJoKiNVV+zvW2xM13gNdFhAEpQ9dQJHt3naLvfg+LkF2ldgSLiF4T41lf1SQCM9QmkqPTn4TH568IRagg==",
"dev": true,
"license": "OFL-1.1",
"funding": {
"url": "https://github.com/sponsors/ayuhito"
}
},
"node_modules/@fortawesome/fontawesome-free": {
"version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz",

View File

@ -18,5 +18,9 @@
"mermaid": "^11.12.3",
"rehype-mermaid": "^3.0.0",
"sharp": "^0.34.2"
},
"devDependencies": {
"@fontsource/inter": "^5.2.8",
"react": "^19.2.4"
}
}

View File

@ -2,7 +2,11 @@
import type { Props } from '@astrojs/starlight/props';
import Default from '@astrojs/starlight/components/Head.astro';
import MermaidInit from './MermaidInit.astro';
import { getImagePath } from 'astro-opengraph-images';
const ogImageUrl = getImagePath({ url: Astro.url, site: Astro.site });
---
<Default {...Astro.props}><slot /></Default>
<meta property="og:image" content={ogImageUrl} />
<MermaidInit />

114
docs/src/og-image.tsx Normal file
View File

@ -0,0 +1,114 @@
import React from "react";
import type { RenderFunctionInput } from "astro-opengraph-images";
export async function render({
title,
description,
}: RenderFunctionInput): Promise<React.ReactNode> {
return (
<div
style={{
height: "100%",
width: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
backgroundColor: "#0B1426",
padding: "60px 70px",
fontFamily: "Inter",
}}
>
{/* Signal arc decorations — top right */}
<svg
viewBox="0 0 200 200"
width="200"
height="200"
style={{ position: "absolute", top: "30", right: "50", opacity: 0.15 }}
>
<path
d="M40 180 Q100 60 160 180"
fill="none"
stroke="#FC3D21"
stroke-width="8"
/>
<path
d="M70 180 Q100 90 130 180"
fill="none"
stroke="#FC3D21"
stroke-width="6"
/>
<path
d="M90 180 Q100 120 110 180"
fill="none"
stroke="#FC3D21"
stroke-width="4"
/>
</svg>
{/* Top: site name */}
<div
style={{
display: "flex",
alignItems: "center",
gap: "12px",
color: "#7EB8FF",
fontSize: 28,
fontWeight: 400,
letterSpacing: "0.05em",
}}
>
gr-apollo
</div>
{/* Middle: title + description */}
<div
style={{
display: "flex",
flexDirection: "column",
gap: "16px",
flexGrow: "1",
justifyContent: "center",
}}
>
<div
style={{
color: "#FFFFFF",
fontSize: 56,
fontWeight: 700,
lineHeight: 1.15,
}}
>
{title}
</div>
{description && (
<div
style={{
color: "#8BA4C4",
fontSize: 26,
fontWeight: 400,
lineHeight: 1.4,
}}
>
{description}
</div>
)}
</div>
{/* Bottom: tagline */}
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
borderTop: "1px solid #1E3A5F",
paddingTop: "20px",
color: "#4A6A8A",
fontSize: 20,
}}
>
<span>Apollo Unified S-Band Decoder</span>
<span>GNU Radio 3.10+</span>
</div>
</div>
);
}