Skip to content

Generate per-article OG images for blog posts#71

Merged
HugoGresse merged 2 commits into
mainfrom
blog-og-images
Apr 24, 2026
Merged

Generate per-article OG images for blog posts#71
HugoGresse merged 2 commits into
mainfrom
blog-og-images

Conversation

@HugoGresse
Copy link
Copy Markdown
Member

Summary

Every blog article shared the default OG image because `computeOgPath()` mapped `/blog/*` → `/og/default.png` and the blog layout never set an explicit `metaImage`. Shares from every `/blog/` URL looked identical on Twitter/LinkedIn/Slack.

Changes

  • `src/pages/og/blog/[slug].png.ts` — iterates the `blog` content collection, renders one PNG per article via `pageTemplate` with `eyebrow` = "Blog · " and the article title as headline. Uses the same cache helpers as the other OG endpoints.
  • `src/pages/blog/[...slug].astro` — passes `metaImage={SITE + /og/blog/.png}` to the layout.
  • `src/pages/og/pages/[page].png.ts` — add a `blog` entry so `/blog` index has its own dedicated image instead of falling back.
  • `src/og/ogImage.ts` — route `/blog` to `/og/pages/blog.png`; `/blog/` articles pass their own `metaImage`.

Verified locally

Test plan

  • Build completes and ships `dist/og/blog/*.png`.
  • Social scraper (opengraph.xyz or similar) on `/blog` shows the blog index card.
  • Same on `/blog/2026-cfp` shows the article title + date card, not the default.

🤖 Generated with Claude Code

Every blog article shared the default OG image because
computeOgPath() mapped /blog/* to /og/default.png and the blog page
layout never set an explicit metaImage. Shares from
/blog/<slug> on social platforms all looked identical.

- Add src/pages/og/blog/[slug].png.ts that iterates the blog content
  collection and renders one PNG per article via pageTemplate, using
  the article title and locale-formatted date as copy.
- Wire src/pages/blog/[...slug].astro to pass metaImage pointing at
  that endpoint.
- Add a dedicated /og/pages/blog.png for the blog index (was still
  falling through to default).
- Update computeOgPath: /blog now returns the blog index OG image;
  /blog/<slug> articles pass an explicit metaImage from the page
  frontmatter so the fallback path is only used for unknown routes.

Build regenerates 7 blog PNGs + 1 blog index PNG.

Co-Authored-By: Claude Opus 4 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 24, 2026 06:22
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 24, 2026

Visit the preview URL for this PR (updated for commit 90b24e6):

https://sunnytechwebsite--pr71-blog-og-images-ohnus1bz.web.app

(expires Fri, 01 May 2026 07:03:44 GMT)

🔥 via Firebase Hosting GitHub Action 🌎

Sign: b96ac7ab85879442bb94dc448b41aeca0f34d16c

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Generates unique Open Graph (OG) images for individual blog articles and ensures the blog index uses a dedicated OG image, so social shares no longer all show the default card.

Changes:

  • Add a new OG image endpoint that prerenders one PNG per blog post (/og/blog/<slug>.png).
  • Set blog article pages to pass an explicit metaImage pointing to their per-article OG PNG.
  • Add a dedicated OG image for the /blog index and route /blog to it in OG path computation.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

File Description
src/pages/og/pages/[page].png.ts Adds a blog page spec so /og/pages/blog.png can be generated.
src/pages/og/blog/[slug].png.ts New endpoint to generate per-article OG PNGs from the blog content collection.
src/pages/blog/[...slug].astro Passes per-article metaImage to the layout for correct OG tags.
src/og/ogImage.ts Routes /blog to the blog index OG image and keeps /blog/* fallback behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +24 to +27
export const GET: APIRoute = async ({ params, props }) => {
const cacheKey = `blog/${params.slug}.png`
const cached = await readOgCache(cacheKey)
if (cached) return pngResponse(cached)
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The OG cache key is only based on the slug (blog/<slug>.png). In CI, .og-cache is restored across builds, and reads are enabled, so updating a post’s title/date/content won’t invalidate the cached PNG and the OG image can become stale in production.

Consider versioning the cache key with some representation of the post inputs (e.g., hash of props.title + props.date, or a content digest passed from getStaticPaths) so content edits force a re-render while keeping the public URL stable.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot apply changes based on this feedback

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 90b24e6. The cache key now includes an 8-character SHA-256 hash of props.title + props.date, so any edit to a post's title or date produces a new key and forces a re-render, while the public URL (/og/blog/<slug>.png) remains unchanged.

@HugoGresse HugoGresse merged commit f22a7ba into main Apr 24, 2026
3 checks passed
@HugoGresse HugoGresse deleted the blog-og-images branch April 24, 2026 07:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants