# 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 2026-11-08 · https://shareglint.com/en/guides/how-dev-to-github-generate-og-images

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:

```html
<!-- 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](https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/customizing-your-repositorys-social-media-preview)).

## DEV: a resized cover, or a generated card

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

```text
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](https://developers.forem.com/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](https://shareglint.com/en/guides/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](https://shareglint.com/en/guides/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](https://shareglint.com/en/guides/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.
