ehsan.blog
~/blog/how-to-generate-an-og-image-from-an-svg-template — zsh
cat how-to-generate-an-og-image-from-an-svg-template.md

How to generate an Open Graph image from an SVG template

·5 min read

Paste a link to your site into Slack, X, LinkedIn, or iMessage, and the platform unfurls a preview card with a big image. That image is the Open Graph image (“OG image”), and if you don’t provide one you get a blank card or, worse, a random cropped screenshot. A good OG image is free marketing on every share, so it is worth getting right.

This site has one committed at public/og-image.png, and the blog has its own at blog/public/og-image.png. Both are exactly 1200x630 pixels. Let me explain what that file is, how the page points at it, and how I produce it from an SVG template.

What an OG image actually is

“Open Graph” is a small set of <meta> tags in your HTML <head> that tell social platforms how to render a link. They live alongside the rest of your SEO metadata — see adding RSS, a sitemap, and SEO metadata to Astro. The image tag is the star of the show. Here is what this site declares in index.html:

html
<!-- index.html -->
<meta property="og:image" content="https://developerehsan.com/og-image.png" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:type" content="image/png" />

The og:image points at a real, publicly reachable PNG. Because it lives in public/, the build copies it to the site root, so https://developerehsan.com/og-image.png just works. The width/height tags let platforms lay out the card before the image finishes downloading.

Why 1200x630? That is the standard OG aspect ratio (1.91:1). Every major platform crops to roughly that, so designing at exactly 1200x630 means nothing important gets chopped off. Confirm your file with any image tool:

bash
file public/og-image.png
# public/og-image.png: PNG image data, 1200 x 630, 8-bit/color RGBA

Why design it as an SVG, then export to PNG

Platforms want a raster image — a PNG or JPG. They will not render an SVG in a preview card. So why start with SVG?

Because SVG is text you can edit. When my tagline or role changes, I do not want to reopen a design tool, nudge pixels, and re-export. I want to change one line of markup. SVG gives you:

  • Crisp text at any size (it is vector, so no blur).
  • Version-control-friendly diffs (it is just XML).
  • Trivial edits — change a string, swap a color token.

Then a one-time export bakes it into the PNG the platforms need.

Design the template

An OG image is mostly a background, a headline, and a subtitle. Here is the shape of a template sized to the exact canvas:

plaintext
<!-- scratchpad/og-image.svg -->
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="630"
     viewBox="0 0 1200 630">
  <rect width="1200" height="630" fill="#0a0a0a" />
  <text x="80" y="300" fill="#fafafa"
        font-family="JetBrains Mono, monospace"
        font-size="72" font-weight="700">
    Ehsan Shahid
  </text>
  <text x="80" y="380" fill="#a1a1aa"
        font-family="JetBrains Mono, monospace" font-size="36">
    Full-stack developer
  </text>
</svg>

Points worth internalizing as a beginner:

  • The root width, height, and viewBox are all 1200 x 630. Match them so nothing scales unexpectedly.
  • Coordinates are top-left origin. y="300" on <text> is the baseline of the text, not its top — so large text needs a fairly large y.
  • Keep meaningful content away from the edges (I start at x="80"). Some platforms overlay UI or round the corners.
  • Match your brand fonts and colors. This site’s aesthetic is a dark terminal look with JetBrains Mono, so the template mirrors that.

Render the SVG to a 1200x630 PNG

Here is the honest part, because the style guide I write to demands accuracy: there is no committed script in this repo that generates the OG image. The public/og-image.png and blog/public/og-image.png files are committed artifacts, and the project’s own guidance describes regenerating the OG image from a scratchpad SVG — a manual step, not part of bun run build. I am not going to pretend a package.json script exists when it does not.

So the workflow is: keep the SVG in a scratch location, and export it to PNG whenever the design changes. Any of these does the job.

Using a headless browser or an SVG renderer library (sharp is the common choice in Node):

bash
bun add -d sharp
js
// one-off export script (not committed)
import sharp from "sharp"
import fs from "node:fs"

const svg = fs.readFileSync("scratchpad/og-image.svg")
await sharp(svg)
  .resize(1200, 630)
  .png()
  .toFile("public/og-image.png")

sharp reads the SVG buffer, enforces the 1200x630 canvas, and writes a PNG. If you would rather avoid a dependency, most vector editors (Inkscape, Figma) and even the browser can export an SVG to PNG at a fixed size:

bash
# with Inkscape installed
inkscape scratchpad/og-image.svg \
  --export-width=1200 --export-height=630 \
  --export-filename=public/og-image.png

Whichever tool you use, the output is the same: a static PNG dropped into public/ so the og:image URL resolves.

Verify it works

After deploying, confirm the tags and image are live. Curl the page and look for the meta tags, then check the image loads:

bash
curl -s https://developerehsan.com | grep 'og:image'
curl -sI https://developerehsan.com/og-image.png | grep -i content-type

For the real preview, paste your URL into a social platform’s share box, or use a link-preview debugger. Note that platforms cache aggressively — if you update the image, the old one may show until the cache expires.

What to remember

  • An OG image is the 1200x630 PNG social platforms show in link previews; you wire it up with og:image meta tags in your <head>.
  • Design at exactly 1200x630, keep important content away from the edges, and match your brand fonts and colors.
  • Author it as an SVG template so edits are one-line text changes, then export to PNG — platforms need the raster file.
  • In this repo the PNGs are committed artifacts and regenerating them from an SVG is a manual step; there is no build script that does it automatically.
  • Serve the PNG from public/ so its public URL resolves, and remember platforms cache previews.

Related reading: the OG tags round out the structured metadata in adding Person, ProfilePage, WebSite and FAQPage JSON-LD to a static site, and the committed PNG gets a year-long immutable cache via hardening a Vercel deploy with security and cache headers.

ls ./related
cat ./comments

Comments are not configured yet. Enable GitHub Discussions and paste the giscus repo-id / category-id into src/consts.ts.