Skip to content

fix(seo): render SVG feature images to PNG for og:image, add social preview tags - #768

Merged
stasadev merged 5 commits into
mainfrom
20261026_stasadev_og_png
Oct 26, 2026
Merged

stasadev merged 5 commits into
mainfrom
20261026_stasadev_og_png

Conversation

@stasadev

@stasadev stasadev commented Oct 26, 2026 •

Copy link
Copy Markdown
Member

Short Summary (TL;DR)

Social networks don't show SVG in og:image, so blog posts with an SVG feature image had no preview image when shared. The build now renders a PNG copy of each one, every page gets the twitter:card, og:locale, and og:image dimension tags that https://www.opengraph.to/ reported missing, and preview builds point og:image at their own deployment, so this can be checked before merging.

How This PR Solves The Issue

A new endpoint, src/pages/img/blog/[...path].png.js, uses sharp to render each SVG feature image under /img/blog/ to a 1200px wide PNG at the same path, so banner.svg gets banner.png next to it in dist/ and no image files are committed. The blog post page uses that PNG for og:image and the JSON-LD image, while PNG and JPG feature images stay as they were. The build fails if a PNG with the same name already exists in public/, because it would clash with the generated file.

The BlogPosting JSON-LD image is now an absolute URL on https://ddev.com, as Google's structured data documentation asks.

twitter:card is summary_large_image, so X shows the large image instead of a small thumbnail. og:image:width and og:image:height let Facebook and LinkedIn show the image on a link's first share, and they come from the Astro asset metadata for raster images or from the PNG render for SVGs.

og:image used to be built from site in astro.config.mjs on every build, so a preview pointed at images on https://ddev.com, where a new image returns 404 until merge. Cloudflare preview builds now use CF_PAGES_URL instead, and fork preview builds, which run in GitHub Actions, get their pr-<number> URL as PREVIEW_URL. Canonical URLs stay on https://ddev.com.

Manual Testing Instructions

I tested the previews with https://www.opengraph.to/, which loads the page from outside and reports the tags it finds and whether the image loads.

Release/Deployment Notes

After merge, production og:image stays on https://ddev.com. Social networks may keep a cached preview for posts that were already shared until it is refreshed in their debugger tools.

🤖 Developed with assistance from Claude Code

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Deploying ddev-com-front-end with  Cloudflare Pages  Cloudflare Pages

Latest commit: 606fe2b
Status: ✅  Deploy successful!
Preview URL: https://5c6452ee.ddev-com-front-end.pages.dev
Branch Preview URL: https://20261026-stasadev-og-png.ddev-com-front-end.pages.dev

View logs

@stasadev stasadev changed the title fix(blog): render SVG feature images to PNG for og:image fix(seo): render SVG feature images to PNG for og:image, use preview URLs on previews Oct 26, 2026
@stasadev stasadev changed the title fix(seo): render SVG feature images to PNG for og:image, use preview URLs on previews fix(seo): render SVG feature images to PNG for og:image, add social preview tags Oct 26, 2026
stasadev and others added 5 commits October 26, 2026 17:02
## Short Summary (TL;DR)

Social networks don't show SVG in `og:image`, so blog posts with an SVG feature image had no preview image when shared. The build now renders a PNG copy of each one and uses it for `og:image`.

## How This PR Solves The Issue

A new endpoint, `src/pages/img/blog/[...path].png.js`, uses `sharp` to render each SVG feature image under `/img/blog/` to a 1200px wide PNG at the same path, so `banner.svg` gets `banner.png` next to it in `dist/` and no image files are committed. The blog post page uses that PNG for `og:image` and the JSON-LD `image`, while PNG and JPG feature images stay as they were. The build fails if a PNG with the same name already exists in `public/`, because it would clash with the generated file.

## Manual Testing Instructions

- Open https://20261026-stasadev-og-png.ddev-com-front-end.pages.dev/img/blog/2026/09/banner-ddev-v1-25-4.png and check that it shows the "DDEV v1.25.4 Release" text, not only the logo. SVG text needs fonts in the Cloudflare build image, which is not verified yet.
- View the source of https://20261026-stasadev-og-png.ddev-com-front-end.pages.dev/blog/release-v1-25-4/ and check that `og:image` ends in `banner-ddev-v1-25-4.png`, while https://ddev.com/blog/release-v1-25-4/ still has the `.svg`.
- Paste the preview post URL into the LinkedIn Post Inspector or the Facebook Sharing Debugger and check that the image appears.

🤖 Developed with assistance from [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iew builds

## Short Summary (TL;DR)

On Cloudflare preview builds, `og:image` now uses the preview deployment's own URL instead of `https://ddev.com`, so share checkers such as opengraph.to can load images that only exist on the branch.

## How This PR Solves The Issue

`og:image` was always built from `site` in `astro.config.mjs`, so a preview advertised its images on production, where a new image returns 404 until the branch is merged. `Layout.astro` now uses `CF_PAGES_URL` for `og:image` when Cloudflare Pages builds a branch other than `main`. Canonical URLs and JSON-LD still point at `https://ddev.com`, and fork previews, which are built in GitHub Actions without these variables, keep using `https://ddev.com` as before.

## Manual Testing Instructions

- View the source of https://20261026-stasadev-og-png.ddev-com-front-end.pages.dev/blog/release-v1-25-4/ and check that `og:image` points at a `*.ddev-com-front-end.pages.dev` URL that loads, while `<link rel="canonical">` is still `https://ddev.com/blog/release-v1-25-4/`.
- Run https://www.opengraph.to/u/20261026-stasadev-og-png.ddev-com-front-end.pages.dev/blog/release-v1-25-4 and check that the image loads.
- Check that https://ddev.com/blog/release-v1-25-4/ is unaffected after merge, with `og:image` on `https://ddev.com`.

🤖 Developed with assistance from [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ew builds

## Short Summary (TL;DR)

Fork previews are built in GitHub Actions, which doesn't set `CF_PAGES_URL`, so their `og:image` still pointed at `https://ddev.com`. The fork build now passes its `pr-<number>` preview URL as `PREVIEW_URL`, and `Layout.astro` uses it for `og:image`.

## Manual Testing Instructions

- On the next fork PR, view the source of a page on `https://pr-<number>.ddev-com-fork-previews.pages.dev/` and check that `og:image` points at that host.

🤖 Developed with assistance from [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
## Short Summary (TL;DR)

opengraph.to flagged missing `twitter:card` and `og:image` dimensions, so X showed a small thumbnail card and Facebook or LinkedIn could show no image on a link's first share. Every page now sets `twitter:card` to `summary_large_image`, `og:locale` to `en_US`, and `og:image:width` and `og:image:height`.

## How This PR Solves The Issue

The dimensions come from the Astro asset metadata that `src/lib/images.ts` already loads for raster images under `public/img`, or, for a PNG rendered from an SVG feature image, from the render itself. That render moved into `src/lib/images.ts` and is cached, so the endpoint and the page reporting its size share one result.

🤖 Developed with assistance from [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
## Short Summary (TL;DR)

The `BlogPosting` JSON-LD `image` was a relative path such as `/img/og-default.png`, while Google's structured data documentation asks for absolute URLs. It now resolves against `https://ddev.com`, like the canonical URL.

🤖 Developed with assistance from [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@stasadev
stasadev force-pushed the 20261026_stasadev_og_png branch from 1f4b3b5 to 606fe2b Compare October 26, 2026 15:02
@stasadev
stasadev merged commit 6111249 into main Oct 26, 2026
4 checks passed
@stasadev
stasadev deleted the 20261026_stasadev_og_png branch October 26, 2026 15:06
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.

1 participant