DEV Community

Cover image for Programmatic SEO with hreflang: One Joke, 17 Languages, Server-Rendered
Iurii Rogulia
Iurii Rogulia

Posted on • Originally published at iurii.rogulia.fi

Programmatic SEO with hreflang: One Joke, 17 Languages, Server-Rendered

People type 2+2 into Google. They type 9+10. They type 7*8 when they can't remember whether it's 54 or 56. Each of those is a real, high-volume search query — and most of the results are identical calculator widgets. So when I built Wrongulator, a calculator that returns a confidently wrong answer on purpose, I had a question worth asking: what if every expression were its own page, ranking for the exact arithmetic people already search?

That is programmatic SEO — generating a page per parameter instead of writing pages by hand. And doing it across 17 languages means programmatic SEO with hreflang, where each generated page also declares its 16 translated siblings. The trap is that most programmatic surfaces are thin, duplicative, and get buried by Google. This one isn't, for a specific reason: every page has a real, unique answer baked into the HTML before any JavaScript runs. This post is about how — and the honest costs nobody mentions.

Why a Permalink Per Expression Is Even Possible

A page per expression only works if /2+2 reproduces the same result for everyone, forever, with no database behind it. That property isn't free — it's the result of one design decision I cover in detail in why a viral toy must be wrong the same way every time: the wrong answer is a pure function of the expression, seeded by a stable hash, with no per-user state.

The relevant consequence here is what that property unlocks for SEO. Because f("2+2") always returns the same wrong answer, the server can compute that answer on demand for any expression in the URL, with zero storage. There's no pages table, no CMS, no pre-generation job. A request for /64+5 runs the engine, gets 67 ("the only correct number"), and renders a complete page around it. The programmatic surface is, in effect, infinite — but it costs nothing to hold, because nothing is stored. The pure function is what makes thousands of unique pages possible without a database.

That's the foundation. Everything below is about putting real, crawlable content on top of it.

Why the SEO Has to Be Server-Side

Here's the thing people building client-side toys get wrong: search crawlers and social unfurlers don't run your JavaScript.

When Googlebot fetches /9+10, it reads the raw HTML response. Modern Googlebot can render JS in a second pass, but it's queued, delayed, and unreliable — and social crawlers (Twitterbot, facebookexternalhit, Slack's unfurler) don't render JS at all. They read the HTML once and stop. If your wrong answer only appears after the engine runs in the browser, then to every crawler the page is blank — a generic splash with no content to index and no answer to unfurl.

So the entire indexable payload has to exist in the server's first response: the localized <title> and description, the canonical URL, hreflang alternates for all 17 languages, OpenGraph and Twitter tags, structured data, and — critically — the actual wrong answer as real text in the body. The page has to be done the moment it arrives.

The OG image is part of this story too, but it's its own problem — rendering the share card identically in the browser and on the server is covered in isomorphic Canvas rendering. This article is about the text side: the meta tags, the alternates, the structured data, and the indexable answer in the HTML body.

The Programmatic Surface: Meta, hreflang, and JSON-LD

Three things get computed server-side, per link, before the response goes out.

First, the structured data. Each expression page carries a JSON-LD FAQPage with the real (wrong) answer as the accepted answer. This is what can earn a rich result in search and gives the crawler an unambiguous, machine-readable statement of what the page says:

// server.js — JSON-LD FAQ with the real (wrong) answer, per expression
const jsonld = {
  "@context": "https://schema.org",
  "@type": "FAQPage",
  mainEntity: [
    {
      "@type": "Question",
      name: `What is ${disp}?`,
      acceptedAnswer: {
        "@type": "Answer",
        text: `Wrongulator says ${disp} = ${r.answer}. ${r.reason}`,
      },
    },
  ],
};
Enter fullscreen mode Exit fullscreen mode

Note r.answer and r.reason come straight from the engine, run server-side for this expression. The structured data isn't a static template — it contains the specific wrong answer and its deadpan justification, unique to this page. That's the difference between a FAQPage that helps and one Google ignores as boilerplate. (The FAQPage type is defined by schema.org; whether it shows as a rich result is up to Google.)

Second, the localized meta and hreflang set. A request resolves a locale, and the server emits a localized <title>/description plus a canonical and a full hreflang block pointing at all 17 language variants of the same expression. The shape, reconstructed from the server's response, is:

<!-- per-expression head, computed server-side (illustrative reconstruction) -->
<link rel="canonical" href="https://wrongulator.com/9+10?lang=en" />
<link rel="alternate" hreflang="en" href="https://wrongulator.com/9+10?lang=en" />
<link rel="alternate" hreflang="th" href="https://wrongulator.com/9+10?lang=th" />
<link rel="alternate" hreflang="ja" href="https://wrongulator.com/9+10?lang=ja" />
<!-- … 13 more locales … -->
<link rel="alternate" hreflang="fi" href="https://wrongulator.com/9+10?lang=fi" />
<link rel="alternate" hreflang="x-default" href="https://wrongulator.com/9+10" />
Enter fullscreen mode Exit fullscreen mode

The rule Google documents for hreflang is that the set must be reciprocal — every variant has to list every other variant, including itself, or Google ignores the whole cluster. With 17 languages that's a 17-entry block on each of 17 pages, all generated from one function so they can't fall out of sync. The x-default is the fallback Google serves when none of the listed locales match the user's language.

Third, the sitemap that seeds the surface. Crawlers won't discover /9+10 on their own — nothing links to every arithmetic expression. So a curated sitemap.xml lists the highest-search expressions (2+2, 9+10, 7*8, and so on), each with its hreflang alternates. That's the deliberate part: I don't list every possible expression — that's a combinatorial explosion and a quality problem (more on that below). I list the ones people actually search, and let the long tail be reachable but unseeded.

The payoff of the whole stack: the toy can rank for the exact arithmetic queries people type, in their language, with a result that unfurls into the joke. The pure-function engine is what makes each of those pages cheap; the server-side meta is what makes them indexable.

slug="seo-audit"
text="Programmatic SEO lives or dies on details crawlers see and you don't — reciprocal hreflang, canonical consistency, whether your generated content reads as unique or thin. A technical SEO audit checks exactly that, with a written report."
/>

Localizing a Joke Without Killing It

Seventeen languages is where most products either skip localization or ruin it. A joke run through a translation API isn't funny — it's a sentence that used to be funny. So the i18n layer doesn't translate uniformly. It splits the copy by what the text does.

Prose that merely carries the joke gets translated: the deadpan justifications ("the only correct number"), the UI prompts, the void and malfunction lines. Those need to read naturally in the reader's language, because their job is to set up the punchline, not be it.

But text that is the joke gets left alone. The universal number-memes — skibidi, Ohio, 6-7, 300, 9+10=21 — travel as-is and are funnier untouched. Translating "skibidi" into Finnish would be vandalism. These memes are internet-native; they already crossed languages on their own, and the engine respects that by never touching them.

Then there's the third category, the one that makes the localization feel native rather than merely translated: locale-gated memes that only fire in their own language, because the number means something only in that culture.

// public/i18n.js — split by what the text DOES
// translate: deadpan/ai reasons, void/malfunction lines, UI prompts
// leave alone: universal number-memes; brand labels ("100% WRONG", "HALL OF FAME")
const MEMES = {
  meme555: ['ห้า ห้า ห้า. ตลกจนเครื่องคิดเลขขำ.', ...], // th — only ever shown for th
  meme39:  ['サンキュー。3-9。電卓からの感謝です。', ...],     // ja
  meme666: ['PERKELE. Tämä on ainoa oikea vastaus.', ...],  // fi
};
Enter fullscreen mode Exit fullscreen mode

Each of those is a number-pun that lands in exactly one language. Thai 555 reads as "hahaha" because 5 is pronounced ha. Japanese 39 reads as "thank you" (san-kyū). Finnish 666 triggers perkele, the national curse word, written natively — not "the devil's number" politely translated. A non-speaker would never know these are jokes; a speaker recognizes them instantly. That's the whole point: the toy feels like it was made by someone who actually speaks your language, in 17 languages, because for the locale-gated memes, it effectively was. All 17 — including RTL Arabic — went through a native-speaker, meme-aware review pass. This is the same discipline I apply to production EU storefronts in Next.js i18n at 30 languages: translate the prose, respect what doesn't translate.

The Honest Cost

Programmatic SEO and 17-language localization both sound like pure leverage. They aren't. Here's what they actually cost.

The thin-pages risk is real. Google penalizes mass-produced, low-value pages — "doorway pages" in its terms — and a page-per-parameter surface is exactly the pattern that triggers it. The only reason Wrongulator's pages aren't doorways is that each one has genuinely unique content: a specific wrong answer, a specific deadpan reason, a real FAQPage describing them. If the pages were empty shells with only the expression swapped in the title, this would be a textbook thin-content surface and deserve to be buried. Programmatic SEO is not a trick for ranking empty pages — it's a way to scale pages that would each be worth publishing on their own. Get that backwards and Google is right to punish you.

Seventeen hreflang sets are fragile and expensive. Reciprocity means every page references every locale; one broken or non-reciprocal link can invalidate the cluster. Generating the block from a single function is the only sane way to keep it consistent — hand-maintaining 17×17 alternates would be a guaranteed source of silent bugs.

Native review doesn't scale and doesn't stay done. A locale-gated meme is only funny if a native speaker confirmed it's funny — and idioms drift, slang ages, and a pun that lands today can feel dated in a year. You need a real speaker per language, both to write the joke and to catch the cultural misfire that a translation tool would happily ship. That's 17 people's worth of cultural knowledge, and it's a recurring cost, not a one-time one. Skip it and you don't get a worse joke; you get a dead one, and the reader can tell.

The curated sitemap is manual, on purpose. I could generate a sitemap of millions of expressions. I don't, because most of them have zero search volume and listing them would dilute the surface into exactly the thin-content pattern above. Choosing which expressions to seed is editorial work — you have to know (or look up) what people actually search — and it has to be revisited as search trends shift. The combinatorial space is infinite; the worthwhile slice is small and hand-picked.

None of these are reasons not to do it. They're the reasons it works when most programmatic-SEO attempts don't: the surface is built on real per-page content, the alternates are machine-generated for consistency, and the human parts (memes, sitemap curation) are done by humans who know the domain.

Results

Aspect Approach
Page per Expression — /2+2, /9+10, /7*8 each its own indexable URL
Storage None — pure-function engine computes each page on demand, no database
Indexable content Real wrong answer + deadpan reason in the HTML body before any JS runs
Structured data JSON-LD FAQPage per expression, with the actual wrong answer as the answer
Localized meta <title>, description, canonical computed server-side per locale
hreflang Reciprocal set × 17 languages + x-default, generated from one function
Sitemap Curated — highest-search expressions only, each with hreflang alternates
i18n split Translate carrier prose; leave universal memes; locale-gate native puns
Locale-gated memes Thai 555, Japanese 39, Finnish 666 — fire only in their own language
Review Native-speaker, meme-aware pass on all 17 languages, including RTL Arabic

Takeaways

  1. A pure function is what makes programmatic SEO cheap. If each page's content is computed deterministically from the URL, you need no database and no pre-generation — the surface is effectively infinite and costs nothing to hold. The hard part isn't volume; it's making each page genuinely worth indexing.

  2. If a crawler can't see it without JS, it isn't indexed. Social unfurlers run no JavaScript and search rendering is queued and unreliable. Anything you need ranked — meta, structured data, the actual answer — has to be in the server's first HTML response.

  3. hreflang must be reciprocal and generated, not hand-written. Every locale variant lists every other, including itself, or Google drops the cluster. With many languages, the only maintainable approach is to emit the whole block from one function so it can't drift.

  4. Localize by what the text does, not uniformly. Translate the prose that carries the joke; leave the universal memes alone; locale-gate the puns that only work in one language. Uniform translation flattens humor — and the same principle applies to any voice-driven content, not just jokes.

  5. Programmatic SEO is a quality multiplier, not a quantity trick. It scales pages that would each be worth publishing. Point it at empty shells and you've built a doorway-page farm Google is right to bury. Curate the sitemap, keep each page real, and accept that the native-review cost is the price of doing it well.

The full project — the deterministic Wrong Engine, isomorphic Canvas rendering, this programmatic-SEO surface, and a spoof-proof Hall of Fame — is written up in the Wrongulator project card. For the engine that makes every permalink reproducible, see deterministic wrong; for the share image that unfurls beside this meta, see isomorphic Canvas rendering.

Top comments (0)