Created
April 19, 2026 10:18
-
-
Save mdichtler/c15c538383347d8c46b8ebe286e93ac6 to your computer and use it in GitHub Desktop.
behindthe.dev - Dynamic Image Endpoint
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import satori from 'satori'; | |
| import { Resvg } from '@resvg/resvg-js'; | |
| import { getCollection } from 'astro:content'; | |
| import { readFileSync } from 'node:fs'; | |
| import path from 'node:path'; | |
| export async function getStaticPaths() { | |
| const posts = await getCollection('blog'); | |
| const paths = []; | |
| for (const post of posts) { | |
| const slug = post.id.replace(/\.[^/.]+$/, ''); | |
| paths.push({ params: { slug }, props: { post, isThumb: false } }); | |
| paths.push({ params: { slug: `thumb-${slug}` }, props: { post, isThumb: true } }); | |
| } | |
| // handle non blog pages overwrites | |
| const extraPages = [ | |
| { slug: 'default', title: 'behindthe.dev | Goats & Engineering', tags: ['dev'] }, | |
| { slug: 'about', title: 'About | Martin Dichtler', tags: ['bio'] }, | |
| { slug: 'terms', title: 'Terms of Service | behindthe.dev', tags: ['legal'] }, | |
| { slug: 'privacy', title: 'Privacy Policy | behindthe.dev', tags: ['legal'] }, | |
| ]; | |
| for (const page of extraPages) { | |
| const postMock = { data: { title: page.title, tags: page.tags } }; | |
| paths.push({ params: { slug: page.slug }, props: { post: postMock, isThumb: false } }); | |
| paths.push({ params: { slug: `thumb-${page.slug}` }, props: { post: postMock, isThumb: true } }); | |
| } | |
| return paths; | |
| } | |
| export async function GET({ props }: { props: { post: any; isThumb: boolean } }) { | |
| const { post, isThumb } = props; | |
| const title = post.data.title; | |
| const tag = (post.data.tags?.[0] || 'dev').toLowerCase(); | |
| // Color palette for categories | |
| const palettes: Record<string, { bg: string; accent: string; secondary: string }> = { | |
| // Add new categories / palettes that will be inserted based on markdown tags | |
| default: { bg: '#0b1120', accent: '#38bdf8', secondary: '#0ea5e9' }, | |
| }; | |
| const { bg, accent, secondary } = palettes[tag] || palettes.default; | |
| const noiseFilter = ` | |
| <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 200'> | |
| <filter id='n'> | |
| <feTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/> | |
| <feColorMatrix type='saturate' values='0'/> | |
| </filter> | |
| <rect width='100%' height='100%' filter='url(#n)' opacity='0.15'/> | |
| </svg> | |
| `; | |
| const noiseDataUri = `data:image/svg+xml;base64,${Buffer.from(noiseFilter).toString('base64')}`; | |
| // fonts | |
| const boldFont = readFileSync(path.join(process.cwd(), 'public/fonts/atkinson-bold.woff')); | |
| const semiBoldFont = readFileSync(path.join(process.cwd(), 'public/fonts/atkinson-bold.woff')); | |
| const svg = await satori( | |
| { | |
| type: 'div', | |
| props: { | |
| style: { | |
| height: '100%', | |
| width: '100%', | |
| display: 'flex', | |
| flexDirection: 'column', | |
| justifyContent: 'center', | |
| padding: '80px', | |
| backgroundColor: bg, | |
| backgroundImage: `radial-gradient(circle at 100% 0%, ${accent}33, transparent), radial-gradient(circle at 0% 100%, ${secondary}22, transparent)`, | |
| color: 'white', | |
| fontFamily: 'Atkinson', | |
| position: 'relative', | |
| overflow: 'hidden', | |
| }, | |
| children: [ | |
| // The "Grainy" layer | |
| { | |
| type: 'div', | |
| props: { | |
| style: { | |
| position: 'absolute', | |
| inset: 0, | |
| backgroundImage: `url(${noiseDataUri})`, | |
| opacity: 0.25, | |
| backgroundRepeat: 'repeat', | |
| }, | |
| }, | |
| }, | |
| // Accent line | |
| { | |
| type: 'div', | |
| props: { | |
| style: { | |
| position: 'absolute', | |
| top: 0, | |
| left: 0, | |
| height: '8px', | |
| width: '100%', | |
| background: `linear-gradient(to right, ${accent}, ${secondary})`, | |
| }, | |
| }, | |
| }, | |
| // Branding Header | |
| { | |
| type: 'div', | |
| props: { | |
| style: { | |
| position: 'absolute', | |
| top: '60px', | |
| left: '80px', | |
| display: 'flex', | |
| alignItems: 'center', | |
| gap: '12px', | |
| }, | |
| children: [ | |
| { | |
| type: 'div', | |
| props: { | |
| style: { | |
| width: '24px', | |
| height: '24px', | |
| borderRadius: '6px', | |
| backgroundColor: accent, | |
| } | |
| } | |
| }, | |
| { | |
| type: 'div', | |
| props: { | |
| style: { | |
| fontSize: '28px', | |
| fontWeight: '800', | |
| letterSpacing: '-0.02em', | |
| opacity: 0.8, | |
| }, | |
| children: 'behindthe.dev', | |
| }, | |
| } | |
| ] | |
| }, | |
| }, | |
| // Tag badge | |
| { | |
| type: 'div', | |
| props: { | |
| style: { | |
| fontSize: '24px', | |
| fontWeight: '600', | |
| marginBottom: '16px', | |
| color: accent, | |
| textTransform: 'uppercase', | |
| letterSpacing: '0.15em', | |
| }, | |
| children: tag, | |
| }, | |
| }, | |
| // Main Title | |
| { | |
| type: 'div', | |
| props: { | |
| style: { | |
| fontSize: '88px', | |
| fontWeight: '800', | |
| lineHeight: 1.1, | |
| letterSpacing: '-0.04em', | |
| maxWidth: '1000px', | |
| display: 'flex', | |
| flexDirection: 'column', | |
| }, | |
| children: title, | |
| }, | |
| }, | |
| ], | |
| }, | |
| }, | |
| { | |
| width: 1200, | |
| height: 630, | |
| fonts: [ | |
| { | |
| name: 'Atkinson', | |
| data: boldFont, | |
| weight: 700, | |
| style: 'normal', | |
| }, | |
| { | |
| name: 'Atkinson', | |
| data: semiBoldFont, | |
| weight: 600, | |
| style: 'normal', | |
| }, | |
| ], | |
| } | |
| ); | |
| const resvg = new Resvg(svg, { | |
| fitTo: { | |
| mode: 'width', | |
| value: isThumb ? 800 : 1200, | |
| }, | |
| }); | |
| const pngData = resvg.render(); | |
| const pngBuffer = pngData.asPng(); | |
| return new Response(new Uint8Array(pngBuffer), { | |
| headers: { | |
| 'Content-Type': 'image/png', | |
| 'Cache-Control': 'public, max-age=31536000, immutable', | |
| }, | |
| }); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment