Every link we had ever shared rendered as a cropped slice of our own logo. The image was 1695 × 387 — a 4.38:1 strip — and the card type our pages declare renders at about 1.91:1. Nothing warned us, because nothing in that pipeline treats a wrongly shaped image as an error.
Before building a replacement we measured nine other browser-game portals to see what a correct one looks like in the wild. Six of the nine served a measurable image, and exactly one of those matched both the card type it declared and its own declared dimensions. Ours was the worst number in the table.
The short version. A social card has a fixed shape. Your image gets fitted into it and the remainder is discarded — silently, with no error and no console message. So the defect is invisible from inside your own site: the page validates, the tag is present, the image loads. You only ever see it on somebody else's platform, in a preview you are not looking at.
What shape is an og:image supposed to be?
It depends on one other tag, which is the part people miss. twitter:card chooses which of two layouts a platform uses:
-
summary_large_image— the big banner card. It renders at roughly 1.91:1, and 1200 × 630 is the conventional size to supply. -
summary— the small card with a thumbnail beside the text. Roughly square.
Declaring one and supplying an image shaped for the other is the whole bug. And because the platform fits rather than rejects, both mistakes look completely fine on your end. The tag is there. The image resolves. Your validator is happy. Somewhere else, a person sees the middle third of your wordmark.
Worth saying plainly, since these tags have a reputation for being fiddly: the ratio is what matters, not the exact pixel count. A 2400 × 1260 image is the same shape as a 1200 × 630 one and previews identically.
What was our own site actually serving?
We read the number out of the PNG's own IHDR header rather than trusting the filename or the CSS, and by two independent routes — once off the file on disk, once by fetching the first 16 KB of the live URL. Both gave the same answer.
| Measurement | Value |
|---|---|
The file we were serving as og:image
|
the site wordmark, 36,527 bytes |
| Its real pixel size | 1695 × 387 |
| Its aspect ratio | 4.380 : 1 |
| What our declared card renders at | ~1.905 : 1 |
| Mismatch | 2.30× |
| Pages affected | 23 of 235 |
og:image:width / og:image:height declared |
neither |
At 2.3× too wide, more than half the width of the image is cut. What survives is a centre slice of a wordmark with the first and last letters gone — which is worse than no image at all in one specific way: it looks like a rendering bug on the platform, not a choice.
What do other browser-game portals serve?
This is the part we had not seen written down anywhere, so we measured it. One unauthenticated GET per homepage, the Open Graph tags read out of the served HTML, then a Range: bytes=0-16383 request against each image so we could read its real dimensions out of the file's own header without downloading the whole thing. Measured 12 August 2026.
| Site | Card declared | og:image, real size | Ratio | Dimensions declared in the markup |
|---|---|---|---|---|
| Poki | summary_large_image |
1200 × 630 | 1.905 | 1200 × 630 — matches |
| Playgama | summary |
2400 × 1260 | 1.905 | none |
| CrazyGames | summary_large_image |
1200 × 600 | 2.000 | 1200 × 630 — does not match |
| Armor Games | summary_large_image |
1200 × 825 | 1.455 | none |
| Y8 | summary_large_image |
500 × 500 | 1.000 | 500 × 500 — matches the file |
| itch.io | summary_large_image |
no og:image | — | — |
| Kongregate | none | no og:image | — | — |
| GameJolt | none | no og:image | — | — |
| Newgrounds | not measured — returned 403 to our request | |||
| Ours, before this | summary_large_image |
1695 × 387 | 4.380 | none |
Reading it honestly, four things stand out:
- One site got it exactly right. Poki declares the wide card, serves 1200 × 630, and declares dimensions that match the file. That is the whole specification, done.
- Y8 has our bug, smaller. It declares the wide card and serves a square 500 × 500 logo. Same class of mismatch as ours, at 1.9× instead of 2.3×.
-
A declared dimension can drift from the file. CrazyGames' markup says the image is 630 tall; the file the CDN actually delivered to us is 600. The image URL itself even asks for
height=630&fit=crop. Thirty pixels is cosmetically nothing — but it is a live example of a hardcoded number and a real file disagreeing, which is the argument for generating those tags rather than typing them. -
Three of nine ship no og:image on the homepage at all, and itch.io declares
summary_large_imagewhile supplying nothing to put in it. A card with a hole in it.
What this table is not. It is not a ranking, and it is not a criticism of anyone. Every site in it is a larger platform than ours, and the worst number in the table is ours. It is also a single unauthenticated request per homepage on one day: a site can serve different markup to a logged-in visitor or to a specific crawler, inner pages often differ from the homepage, and Newgrounds returned 403 to us so it is recorded as unmeasured rather than guessed at. The point of the table is only this — if a defect this visible is this common among sites this large, checking yours is worth five minutes.
Why did measuring first cut the job by ninety per cent?
The instinct on finding a broken share image is to build a card and put it on every page. We have 235 pages, so that is 235 edits.
Counting first said otherwise. 212 of the 235 were never part of the defect. Those pages point og:image at the individual game's own thumbnail rather than at any site-level image. We sampled 25 of the 200 catalogue thumbnails at random by range request, and 25 of 25 came back 1920 × 1080; a full sweep of all 200 resolved 196 at that size and left 4 unresolved.
16:9 is 1.778:1. Into a 1.91:1 card that is a trivial crop — a few per cent off the top and bottom. Those pages were fine.
They were also better than fine. A preview showing the actual game tells a reader what they are about to click; a brand card tells them the domain, which the platform already prints underneath. Putting a generic card on 212 game pages would have been a downgrade dressed up as a fix — the kind you never detect afterwards, because everything you changed still validates.
So 23 pages needed the new card, not 235. The general rule we took from it:
Count how many things actually have the defect before you build the thing that repairs it. The audit is not preparation for the fix. It is the first draft of the fix, and it is the step that stops a repair doing damage of its own.
Why we did not declare width and height on every page
og:image:width and og:image:height let a platform lay out the preview before it has fetched the image. That is a genuine benefit and we do declare them — on the 23 pages using our own card.
We deliberately did not add them to the 212 pages whose images live on someone else's CDN. Of 200 thumbnails, 4 did not resolve: three whose dimensions were not inside the first 8 KB, and one that timed out. Writing 1920 × 1080 on all 200 would mean publishing a number we had not verified about a remote file that can change without telling us. Leaving the tags off costs nothing, because platforms measure the image once they fetch it.
Our own card's tags are safe for a different reason: the build reads them out of the PNG header at generation time, so the declared number cannot drift from the asset. Which is exactly the failure mode the CrazyGames row above is a live example of.
The underlying principle is the same one that governs every other number we publish: a dimension you have not measured is a claim, not a fact.
Why was it the hand-written pages again?
All 17 of our blog posts carried the wide wordmark. That is not a coincidence and it is not carelessness — it is the same structural hole that had just cost us three untagged pages, described in an earlier post about missing analytics tags.
The mechanism, in one sentence: a hand-authored page starts life as a copy of an older page's <head>, so it inherits whatever that head contained — and the generator deliberately never overwrites a file a human wrote, so the template can never reach in and correct it. Every hand-written page is a snapshot of your head element on the day it was created.
The repair is a build step rather than a checklist item, because a checklist still depends on somebody remembering on file number eighteen. It is deliberately narrow: it only touches a page whose og:image is one of our own logo files. Give a post a real cover image of its own and it is left completely alone.
A gap we found while writing this, and have not closed. All 17 posts now serve the new card as their
og:image— but theBlogPostingstructured data on those same 17 pages still names the 1695 × 387 wordmark as itsimage. Two tags on one page, disagreeing about what the page's image is. The repair covered the one we went looking at and not the one beside it. We are writing it down rather than quietly fixing it later, because the whole argument of this article is that the assumption is the expensive part: we assumed a fix aimed atog:imagehad covered the page's images. It had not. This post uses the card in both.
What is worse than a cropped preview?
A missing one. A cropped image looks like a glitch; a preview with no image at all looks like a dead link, and it is the exact failure you invite when you repoint every page at a brand-new file that has not shipped yet.
So the card is protected three separate ways, none of which trusts the others:
- The build throws a fatal error if the card is missing from source, before it writes anything.
- The generator copies the card into the output as part of the build rather than relying on someone to remember it.
- The bundle assembler asserts that every same-host
og:imageresolves to a real file in the bundle before it can be shipped.
And each of those assertions was proved able to fail — run against a deliberately broken copy of the site to confirm it catches the break, then against the clean one to confirm it still passes. A check that has never failed is not a check; it is a line of code you feel good about.
How do you check yours in five minutes?
-
Read your own markup from outside.
curl -s https://yoursite.com/ | grep -o 'og:image[^>]*'— and note yourtwitter:cardvalue in the same pass, because the two only make sense together. - Measure the file, not the CSS. Fetch the first 16 KB and read the header. For a PNG that is four bytes at a known offset:
import urllib.request, struct
req = urllib.request.Request(IMG_URL, headers={"Range": "bytes=0-16383"})
b = urllib.request.urlopen(req).read()
w, h = struct.unpack('>II', b[16:24]) # PNG IHDR
print(w, h, round(w / h, 3))
- Divide. Anything outside roughly 1.7 – 2.1 will visibly crop in a wide card. A wordmark will be somewhere around 4. A square logo will be 1.0.
- Check the image resolves from outside your network, on a clean request. A 404 here is worse than a bad ratio.
- If you declare width and height, confirm they match the file. They drift, as the table above shows.
- Then do your second-most-shared page type, not just the homepage. This is the step that actually pays. Ours was fine on 212 pages and broken on 23 — and the 23 were the ones anybody shares.
- Control your own probe. Point the same script at a URL you know is not an image and at one you know does not exist. If it returns a confident answer for both, it is not measuring anything.
That last one is not padding. The reason this defect lived on our site for as long as it did is that everything we had ever pointed at it reported success.
What we could not verify
- Newgrounds is unmeasured. It returned 403 to our request. That is our access, not their markup, and we are not going to guess a row.
- We measured homepages, once, on one day. Inner pages commonly differ, and a site may serve different markup to a logged-in visitor or a named crawler.
- Whether any of this improves click-through is unknown, and we are not going to claim it. We have no before-baseline for share-card clicks, so the honest status is unmeasured rather than improved.
-
The fix is not live on our own site yet. It is complete and verified in our deploy bundle, but the machine that built it has no deploy path, so as of publishing this the live site still serves the 4.38:1 wordmark — we re-checked, and the new card returns 404 on live. Saying so is simpler than the alternative, and you can verify it yourself with the
curlin step 1.
Written while rebuilding the share cards on SlowDen, a place to put a browser game where people can find it and play it — no download for the player, no store page for you. Games there come from creators, partners and third-party sources, and each one is credited to whoever made it. If you have something playable, you can submit it or take a Slow Cook prompt: selected entries have a chance to be featured and selected winners may receive a shoutout, which is the honest list and there is nothing guaranteed beyond it.
Top comments (0)