Conversation
There was a problem hiding this comment.
Pull request overview
Adds dedicated, prerendered Open Graph (OG) image endpoints for key pages and wires them into page metadata so link previews render with the updated design.
Changes:
- Introduces new prerendered OG PNG routes for the home page, blog index, authors index, and author detail pages.
- Refactors OG image rendering/layout by adding shared shells/components and a shared font loader.
- Updates
BaseHeadand several pages to pass explicit OG image URLs into the layout/head metadata.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/pages/og/index.png.ts | New prerendered OG image endpoint for the homepage. |
| src/pages/og/blog.png.ts | New prerendered OG image endpoint for the blog index page. |
| src/pages/og/authors/index.png.ts | New prerendered OG image endpoint for the authors index page. |
| src/pages/og/authors/[author].png.ts | New prerendered OG image endpoint for individual author pages. |
| src/pages/og/[...slug].png.ts | Switches post OG image route to use shared font loader. |
| src/pages/index.astro | Passes homepage OG image into BaseLayout. |
| src/pages/blog/index.astro | Passes blog OG image into BaseLayout. |
| src/pages/blog/[page].astro | Passes blog OG image into paginated blog pages. |
| src/pages/authors/index.astro | Passes authors index OG image into BaseLayout. |
| src/pages/authors/[author].astro | Passes author OG image + description into BaseLayout. |
| src/lib/og-image.tsx | Adds shared OG layout shell plus new page/author OG image components. |
| src/lib/og-fonts.ts | Adds shared Inter font loader for Satori rendering. |
| src/components/BaseHead.astro | Uses dev origin for OG image absolute URLs (while keeping prod behavior). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| return new Response(new Uint8Array(png), { | ||
| headers: { | ||
| "Content-Type": "image/png", | ||
| "Cache-Control": "public, max-age=31536000, immutable", |
There was a problem hiding this comment.
The OG PNG routes are not content-hashed URLs, but they are served with Cache-Control: ... max-age=31536000, immutable. That combination can cause clients/CDNs to keep serving stale images after a deploy (e.g., when the blog post count changes). Consider removing immutable and/or using a shorter max-age, or introducing a versioned URL strategy so long-lived immutable caching is safe.
| "Cache-Control": "public, max-age=31536000, immutable", | |
| "Cache-Control": "public, max-age=3600", |
| let fontsPromise: Promise< | ||
| { name: string; data: ArrayBuffer; weight: 400 | 600 | 700; style: "normal" }[] | ||
| > | null = null; | ||
|
|
||
| export function getFonts() { | ||
| if (!fontsPromise) { | ||
| fontsPromise = Promise.all([ | ||
| loadFont(400).then((data) => ({ name: "Inter" as const, data, weight: 400 as const, style: "normal" as const })), | ||
| loadFont(600).then((data) => ({ name: "Inter" as const, data, weight: 600 as const, style: "normal" as const })), | ||
| loadFont(700).then((data) => ({ name: "Inter" as const, data, weight: 700 as const, style: "normal" as const })), | ||
| ]); | ||
| } | ||
| return fontsPromise; |
There was a problem hiding this comment.
fontsPromise is declared as Promise<...> | null, so getFonts() currently returns a nullable type. Even though the implementation always initializes the promise before returning, the nullable return type can leak into callers and allow null to be passed to satori without a type error. Consider changing this to let fontsPromise: Promise<...> | undefined (or similar) and having getFonts() explicitly return Promise<...> (non-nullable).
| const resvg = new Resvg(svg, { fitTo: { mode: "width", value: 1200 } }); | ||
| const png = resvg.render().asPng(); | ||
|
|
||
| return new Response(new Uint8Array(png), { |
There was a problem hiding this comment.
resvg.render().asPng() already returns a Uint8Array; wrapping it in new Uint8Array(png) creates an extra allocation/copy. You can pass png directly to Response to avoid unnecessary work during build/prerender.
| return new Response(new Uint8Array(png), { | |
| return new Response(png, { |
before


after

