Build1 distinct publisher3 min readUpdated
A Next.js file convention generated eleven locale routes that had never returned a 200. The only monitor that noticed was Google's crawler, and it had only tried one of them.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
The mechanism worth taking away is the gap between what generates a route and what exercises it. `app/[locale]/opengraph-image.tsx` is a file convention: you do not import it, you do not link to it, and no page renders it, according to the author's account [2]. Its only output is a meta tag pointing at a route Next builds on your behalf [2]. So the normal feedback loop has nothing to grab. Clicking through the site shows nothing, because no page requests the image [3]. The build compiles, because the failure happens per request at render time [4]. Deploy checks that fetch pages and grep for text skip it, because it is not a page [5]. And the user-visible symptom lands on someone else's server: a blank preview card on a social site [6].
The failure itself is small enough to be invisible in review. `next/og` renders through Satori, which requires a div with more than one child to declare `display: flex` or `display: none` [7]. The offending line reads as one sentence to a person and as two JSX children to the compiler: the expression container and the adjacent text node [8]. Every other div in the file either declared display or genuinely had one child, so the broken one looked like its neighbours [9].
Then the diagnosis gets pushed two layers away from the code. `ImageResponse` streams, so by the time Satori throws, headers are sent and the body has started; Next cannot render a 500 page and closes the connection [10]. nginx, still waiting on the upstream, logs a premature close and returns 502 [11]. That is infrastructure vocabulary. It sends you to memory, to the process, to the proxy config, when the cause is a JSX child count [12].
The fix the author chose was not the one the error message asks for. Wrapping the content in a single template literal makes it one text node, so the multi-child rule never applies [13]. Adding `display: flex` also works, but it turns the text into a flex item and changes how it wraps against the `maxWidth: 1000` [14]. One of those fixes the crash and leaves layout alone.
The blast radius was not uniform, and that is the part the author says turned a curiosity into a bug. Pages that set `openGraph.images` explicitly override the file convention and were fine; pages that did not, including the privacy policy and terms pages in all eleven locales, inherited the broken route and published a 502 as their og:image [15]. That is 22 pages serving a dead image reference on a site of roughly 1,700 pages [16][1], which is why the sampling logic matters more than the ratio: Search Console reports what it happened to crawl, so one 5xx on a locale-parameterised route means at least one [17]. A for-loop over the locale list took the count from 1 to 11 [18].
The durable lesson is the checking discipline, and it is unglamorous: curl the route after you touch it, because nothing in the normal loop covers a file nothing imports [19]. After the change, all eleven locales returned 200 with a 244,560-byte PNG at 1200x630 [20].
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
Search Console showed one URL in the "Server error (5xx)" bucket on a site of about 1,700 pages.
app/[locale]/opengraph-image.tsx is a Next.js file convention: it is not imported, not linked, and no page renders it; its job is to make Next inject a <meta property="og:image"> tag pointing at a route it generates.
The browser could not have caught it, because no page requests the image and clicking through the site shows nothing wrong.
The build could not have caught it: the code compiles fine and the failure is at render time, per request.
The author's deploy checks fetch pages and grep for text, and this route is not a page.
The failure surface for a user is a social preview card being blank on someone else's site, so no user report was likely.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Detailed first-hand account, single unverified source
The account is unusually specific for a single-source story: per-locale HTTP statuses, the verbatim Satori error, the Node and nginx log lines, the offending JSX, the fix, and byte-exact post-fix output. But everything rests on one self-published dev.to post by the site owner, with no independent reproduction, vendor documentation, or third-party confirmation in the supplied material, and the counterfactual 'nothing could have caught it' is scoped to the author's own pipeline.
One site, one operator, fix verified in production
The only real-world footprint in the supplied material is a single site: eleven broken locale routes and 22 inheriting pages, then a verified production fix. There is no evidence in the cluster about how widely this failure mode or the recommended curl-the-route practice occurs elsewhere, so adoption is narrow but concretely observed rather than absent.
Slightly overstated framing, well-grounded substance
The headline generalisation ('no test could have caught it') is broader than the evidence, which shows only that the author's specific browser/build/test/deploy-check loop missed it; a route-level smoke check or synthetic fetch would have caught it, as the author's own first recommendation implies. Offsetting that, the body is measured, self-critical and quantified, and the user-visible impact — 22 pages with a broken og:image on a small hobby site — is presented without inflation.
Mild self-promotion, no vendor or funding stake
The author is the owner of the site under discussion and names it (textmachine.org, browser-based text tools), so the post carries an ordinary developer-marketing benefit on a platform built for that. There is no evidence of vendor sponsorship, affiliate framing, or a commercial position for or against Next.js, Satori or nginx; the piece criticises the author's own pipeline rather than promoting a product.
Coherent but uncorroborated single-source report
Internal coherence is high — the error message, streaming behaviour, nginx log and 502 form a mechanism that hangs together, and the JSX-child explanation matches the quoted Satori constraint. Confidence is capped because the cluster has exactly one publisher and one first-person source, no independent verification of any log line or status code, and no way to distinguish author-specific configuration effects from general framework behaviour.
build
Eight months of JSON as a database: the bill came due on slug identity, not read latency1 distinct publisher
build
8,664 indexed pages, nine clicks: programmatic SEO now fails at ranking, not indexing1 distinct publisher
build
A 5x publishing increase cost one site 1,000 indexed pages and every impression1 distinct publisher
build
Under 30% citation overlap between engines makes pooled AI visibility scores unbuyable1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 22, 2026