Menu
Sign up

How GitHub and DEV generate their social preview images: a teardown from the outside

What GitHub's and DEV's og:image URLs, headers and pictures show about how they generate social preview images, observed from outside, and how to copy it.

Published November 8, 2026 · 4 min read

Some sites have social previews that look made for every page: a GitHub repository shows its name, description and star count; a DEV post shows its title and author. None of that is magic, and much of it can be read from the outside: the og:image URL, the response headers and the picture itself. This page goes through what we saw on 2026-11-08, what it says about how each site works, and how to build the same thing.

What we did. We requested public pages of GitHub and DEV with a social crawler's user agent, read their og:image tags, fetched the images and recorded the headers, sizes and formats. Everything below is observed behaviour, not knowledge of their code, and either site can change it at any time.

GitHub: rendered on demand from its own data

A repository page without a custom image, an issue and a pull request all point at the same service:

<!-- github.com/torvalds/linux -->
<meta property="og:image" content="https://opengraph.githubassets.com/969b087d…e60/torvalds/linux">
<!-- github.com/rails/rails/issues/50000 (a pull request, as it turned out) -->
<meta property="og:image" content="https://opengraph.githubassets.com/eb89be76…325/rails/rails/pull/50000">

What came back:

Repository card Pull request card
Picture 1200×600 PNG, 87 KB 1200×600 PNG, 104 KB
Shows name, description, owner's avatar, contributors, issues, stars, forks, a language bar title in code font, comments, reviews, files, +/− lines, author, date
Cache-Control public, max-age=21600, immutable same
First fetch from cache (x-cache: HIT) in 0.24 s rendered (MISS) in 0.92 s

The 64-character hash at the start of the path is not a signature. Replacing it with 64 zeros returned the identical image (pixel for pixel), and an unknown repository name returned a generic card with the GitHub logo. So the picture is built from what GitHub knows about torvalds/linux, not from the URL. The hash is most likely there to change the URL when the card should change — new stars, a new description: an immutable response is kept for its six hours without being checked again, so only a new URL gets a changed card past the caches. The same hash came back on two page fetches a few minutes apart.

Repositories with an uploaded social preview point elsewhere: repository-images.githubusercontent.com/…, a stored JPEG (1280×640 for Playwright's repository), cached for 30 days. GitHub's own advice for that upload: "a PNG, JPG, or GIF file under 1 MB in size" and "at least 640 by 320 pixels (1280 by 640 pixels for best display)" (GitHub Docs).

DEV: a resized cover, or a generated card

On DEV every post's og:image was a resizing URL:

https://media2.dev.to/dynamic/image/width=1200,height=627,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F….png

The parameters are readable: 1200×627, cropped to fill (fit=cover), the crop centred on what matters (gravity=auto), the format chosen per request (format=auto — our request got WebP, 118 KB). The original image's address is the last part of the path. The response was served from a CDN with max-age=31536000, immutable: a year.

What goes into that URL depends on the post. With a cover image, it is the cover. Without one, the public API (DEV API) listed cover_image: null and a social_image that was an uploaded PNG of a card in DEV's design — the post's title in large type, the author's photo and name, the date. So DEV generates a card once for posts without a cover and stores it as a file, then treats it like any other upload.

The two patterns

GitHub DEV
When the picture is made On request, cached for 6 hours Once, stored as a file
What the URL names The object (owner/repo) plus a cache key The stored file, through a resizer
Where the content comes from GitHub's database The post's cover or a generated card
Changing content New hash, new URL New upload, new URL

Both keep one rule that matters for social previews: a changed picture gets a changed URL. Platforms cache previews per URL (Facebook for up to 30 days, X for about seven; see og:image not showing), so overwriting an image behind the same address does not reach anyone who already has it cached.

Building it yourself

If the content can be looked up from an id, copy GitHub: an endpoint like /og/post/1842.png that reads the post from your database, renders the card and returns it with a long cache time. Put a version in the URL — a hash of the title and the update time — so that editing the post changes the address. No signature is needed: someone can request any post's card, but only with your content.

If the URL carries the text, sign it. An endpoint that renders whatever ?title= says turns into a free image service for anyone. Our signed /i/ URLs work this way: the fields are in the URL, the HMAC proves they came from you, and identical fields are served from cache without a new render (signed image URLs).

If the data is already on the page, our auto-OG URL is the GitHub pattern without an endpoint of your own: /u/{account}.png?url=<page> reads the page's title, description and canonical URL, renders your template, and caches by those fields — an unchanged page is never rendered twice, even under ?utm_ variants. It needs no signature because it only renders pages on domains you have listed. One og:image tag in your layout covers every page.

For the design side — what to put on the card and at which size — see what is an Open Graph image; templates like blog-og and site-card are close to the GitHub and DEV layouts.

FAQ

How does GitHub generate its social preview images?

On demand, from its own data. A repository, issue or pull request page points og:image at opengraph.githubassets.com/<64-character hash>/<owner>/<repo>, which returns a 1200×600 PNG with the name, description, owner's avatar and counts. In our test any 64-character hash returned the same picture, so the hash is a cache key, not a signature: the content comes from GitHub's database, not from the URL.

How does DEV (dev.to) make its og:image?

From the post's cover image when it has one, resized by an image proxy to 1200×627 (served as WebP to our request). For a post without a cover, its API listed a generated card — the title, author and date in DEV's design — stored as an uploaded image and served through the same resizing URL.

Can I build social images the way GitHub does?

Yes, if the image's content can be looked up from an id in the URL: then the URL needs no signature, only a cache key that changes with the content. If the URL itself carries the text, sign it, or anyone can render their own text on your image service.