DEV Community

ihuajiu
ihuajiu

Posted on • Originally published at pokeroll.app

I built a random Pokémon generator with Next.js and PokéAPI — here's what I learned about SEO in 2026


A few weeks ago I shipped PokeRoll, a free random pokemon generator: one tap rolls 1 of 1,000+ Pokémon with stats, abilities and artwork. It also builds a random team of 6 (with gen/region/type filters), runs a shiny hunt at real 1/4096 odds, fuses two Pokémon, and flips every card into a copy-paste-ready Pokémon Showdown set.

This post isn't about the game side, though. It's about what happened when I took SEO seriously on a small static-ish site in 2026 — including the parts of "SEO" that only exist because AI search engines now read your pages too.

The stack: boring on purpose

  • Next.js App Router + ISR. Every page is statically generated and revalidated in the background. After full static-ization, TTFB dropped from ~2.25s to ~100ms. For a site whose entire SEO strategy is "rank for hundreds of long-tail generator keywords," cheap and fast beats clever.
  • PokéAPI as the only data source. It gives you game-level data (base stats, types, moves). One thing it does not have: recommended EV spreads — that's competitive-community knowledge, not game data. I initially went looking for it in the API before realizing the distinction. We ended up with a documented heuristic (252/252/4) instead of integrating an external dataset. Lesson: before wiring up a data source, classify what you're looking at — game data, community knowledge, or your own convention.
  • Programmatic routes for long-tail. /type/[type] × 18 types, /gen/[n] × 9 generations, /by/[region] × 9 regions, all from templates. This is where the SEO fun starts.

Lesson 1: Intent matching doesn't count. Verbatim coverage does.

My first pass at keywords was vibes-based: "the page is obviously about that, Google will figure it out." It won't.

The fix was a three-step audit:

  1. Map every high-frequency query to a page. I dumped the keyword list from Google autocomplete/competitor titles into a text file, then mapped each one to a landing page — enumerating all dynamic route parameters (18 types × 9 gens × 9 regions).
  2. Sort the gaps into three buckets:
    • Copy gap — page exists, keyword doesn't → add it to metadata immediately. Cheapest win there is.
    • Feature gap — keyword needs a feature that doesn't exist (dual-type generator, etc.) → roadmap it.
    • Wrong intent — emulator/ROM/TCG queries → deliberately drop. Not the same product.
  3. Verify with a script, not your eyes. After editing, I grepped every keyword against the generated metadata of its page. This caught real bugs — e.g. my random ${type} pokemon generator template was missing the literal word "type" for one variant, which no human review would have spotted.

Lesson 2: Title real estate is front-loaded

  • Put the high-frequency query at the front of the title. My homepage title used to lead with the brand; the main keyword sat in the middle. Flipped it.
  • Standardize the suffix as keyword | Brand. I deleted a "— Fan-made Tool" suffix: 16 characters carrying zero ranking value.
  • Gotcha worth sharing: I batch-replaced the title suffix across the codebase with replace-all… and the same string appeared in a regex literal used to strip the suffix. The new suffix contained |, which in a regex means "or". Everything silently broke. Before a bulk replace, grep the string in code logic, not just in copy.

Lesson 3: Meta description is a CTR game with a hard window

140–160 characters is the window checkers enforce — too short wastes the snippet, too long gets truncated. Easy for static pages, genuinely fiddly for templates:

  • 18 type names differ by ~5 characters; 9 region game-name pairs differ by ~14. A fixed sentence won't fit all parameter values.
  • Fixes: conditional filler (when the Kalos game name "X & Y" makes the template too short, append "instantly") and length-tiered templates (the share-card description picks one of two templates based on Pokémon name length).
  • Validate by counting real output for every parameter value, min and max — never estimate.
  • Description doesn't rank you; it earns the click. Write a call to action ("copy it to Showdown"), not a summary.

Lesson 4: GEO — optimizing for AI search engines is its own checklist

I ran an AI-search-readiness audit (ChatGPT / Perplexity / AI Overviews citation readiness). What actually moved the needle:

  • llms.txt at the root: site intro, key URLs, data source, contact.
  • FAQPage JSON-LD generated from the same FAQ component that renders the visible page — one source of truth, so the schema can't drift from the content.
  • Real citations only. For "Citations & Quotations" I fetched Bulbapedia's shiny-odds page and PokéAPI's homepage with curl and quoted them verbatim in <blockquote> + <cite>. Don't paraphrase a quote, and never invent one — this took me two audit rounds to pass precisely because round one had links without actual quotations.
  • Structured content means tables and lists. A grid of <div> cards doesn't count. I added a <table> of numbers and an <ol> getting-started list.
  • Author & freshness signals: author + datePublished (first git commit date) + dateModified (refreshed on ISR revalidation) in the WebPage schema, plus a visible byline.
  • Heading hierarchy bugs hide in component libraries — my Pokémon name was an <h3> jumping levels under an <h1>. When you fix the tag, remember the CSS selectors bound to it.

Lesson 5: Distribution is part of shipping

Technical SEO gets you eligible to rank. What gets you into the top 10 is backlinks and user behavior. My current playbook: open-source repo with keyword-rich anchor text, community posts (Reddit/Tumblr), a Product Hunt launch, and share-card URLs that canonical back to the main domain so every shared link consolidates authority instead of fragmenting it across tracking parameters.

One underrated trick: my external links carry ?r=<channel> params for attribution, and every page has a self-referencing canonical — so link equity from all those parameterized URLs rolls up to the clean URL.

The code is open

The whole site is on GitHub: ihuajiu/pokeroll.app — Next.js App Router, ISR, zero backend, zero sign-up. If this was useful, a star helps more than you'd think.

What's your experience with programmatic SEO on small sites? And has anyone actually measured whether llms.txt does anything yet? Genuinely curious.

Top comments (1)

Collapse
 
marcusykim profile image
Marcus Kim

The batch replacement turning | into regex alternation is the kind of tiny SEO change that can quietly break an entire site, so the generated-output checks matter more than the metadata theory. The same discipline shows up in counting every meta description variant across 18 types and 9 regions instead of estimating, and in using ISR to cut TTFB from roughly 2.25 seconds to 100ms. I'd also track impressions and clicks by route family before expanding the programmatic surface; that separates useful long-tail coverage from templates that merely increase the indexed page count.