A game catalog looks like a client-side application: filters, search, cards, detail pages, and playable iframes. It is still a poor reason to ship the complete content database and ask every crawler—or every phone—to reconstruct the page after JavaScript loads.
I recently localized a Nuxt catalog with 153 games into four languages. The public result is 676 indexable routes, but the architecture has three strict properties:
- every indexable route is prerendered as complete HTML;
- missing localized content fails the build instead of falling back to English;
- the browser does not receive Nuxt Content’s SQLite/WASM engine or the full game records.
Here is how those pieces fit together.
Make the route set explicit
Relying only on a crawler means an accidentally missing link can remove a page from the static build. The catalog already has a source of truth, so use it to produce the complete route list.
The base set contains:
- home, popular, new, search, and four site/legal pages;
- one page per category;
- one page per game.
With 153 games and eight categories, that is 169 base routes. A locale mapping applies no prefix to English and /id/, /it/, or /pt-br/ to the other languages. The same English slugs remain stable after the prefix.
169 base routes × 4 locales = 676 indexable routes
Localized 404 pages are also prerendered, but they are noindex and do not enter the sitemap.
This list feeds Nitro prerendering and the sitemap generator. The sitemap is not treated as evidence that pages probably exist; a post-build check resolves every <loc> to a generated HTML file.
Keep one source of truth for product facts
Game titles, iframe URLs, developers, dates, embed types, and stable slugs are product facts. Translators should not rewrite them.
Descriptions, objectives, controls, tips, category copy, and legal text are localized. A generation step combines those translations with the factual fields and writes the Markdown documents Nuxt Content will consume during the build.
For each non-English locale, the generator expects:
- 153 game documents;
- eight category documents;
- four site/legal documents.
That is 165 documents per locale and 495 localized Markdown files in total. Missing keys, extra slugs, duplicate entries, or fact drift are build errors.
The key rule is simple: an indexable localized route cannot silently borrow English body content. A visible fallback is useful in application chrome; it is dangerous when it creates a page advertised to search engines as a different language.
Localize the page, not the embedded game
The catalog can translate its navigation, metadata, controls guide, objectives, and warnings. It does not own every embedded game’s UI.
That boundary should be explicit. The page language changes, while the game iframe may remain in English. Trying to imply otherwise creates misleading metadata and support expectations.
The language switcher should preserve the current route:
/game/ember-vault/
/it/game/ember-vault/
/pt-br/game/ember-vault/
Do not redirect based on Accept-Language or browser settings. Automatic redirects make URLs unstable for crawlers and surprising for people who intentionally chose another language.
Generate SEO signals from the same locale model
Every page needs a self-referencing canonical and a complete alternate set:
-
en; -
id-ID; -
it-IT; -
pt-BR; -
x-defaultpointing to English.
The document lang, Open Graph locale, visible copy, and Schema inLanguage must agree. The sitemap repeats the same alternates.
This is a good place for a deterministic verifier. For all 676 HTML files, check:
- expected
html lang; - exact canonical URL;
- all four
hreflangentries plusx-default; - localized Open Graph locale;
- Schema language;
- no unresolved message keys;
- no known English section headings on non-English pages.
Counting is surprisingly valuable. If the expected route count is 676 and the verifier saw 675, the build fails before deployment.
Split card data from detail data
The full game record contains fields that a grid never needs: iframe URL, long controls, features, developer information, and related-game details. Importing that object into a shared composable can put the entire catalog into a client chunk.
A build step projects a slim card record containing only fields used by search and grids, such as:
slug, title, description, category, tags,
thumbnail, embed type, difficulty, updated date, popular
Detail fields stay server/build-side. The detail route receives one game during prerendering and serializes only what that page needs. Legal and editorial pages do not preload the card catalog merely because they share a layout.
Add a leak test with a known detail-only iframe URL. If that marker appears in a shared JavaScript chunk, the split has regressed.
Remove the client database when all queries are prerendered
Nuxt Content can ship a SQLite/WASM client for browser-side queries. A fully static catalog does not need it if every content query runs during prerender and the result is hydrated from the page payload.
The production build aliases the client database module to a stub that must never execute. Post-build checks then fail if they find:
- SQLite WASM or worker artifacts;
- public content database dumps;
- a runtime fetch for the full catalog;
- old code that overwrites the prerendered description after hydration.
The last item protects more than performance. Runtime replacement can show one description without JavaScript and a shorter, different one with JavaScript, which makes accessibility and indexing unpredictable.
Put budgets around the remaining JavaScript
Internationalization has a real bundle cost. Lazy locale dictionaries help, but only if the application does not preload all languages on every route.
Track both the largest chunk and total emitted JavaScript. Also scan locale chunks for markers from more than one language. A budget is not a universal performance score; it is a tripwire against accidental catalog or dictionary duplication.
The browser check should visit a localized game page directly, before navigating from English. That catches implementations that work only after a locale bundle has already been loaded.
Test the game boundary too
A perfectly localized wrapper is still broken if the play button cannot launch its iframe. For a small set of representative self-hosted games, browser checks should:
- open the localized detail route;
- verify localized initial HTML;
- launch the iframe;
- send a real keyboard or touch input;
- observe game progress rather than elapsed time;
- verify pause, restart, focus, and mobile sizing.
For third-party embeds, record that availability is an external dependency. Do not translate or bundle their binaries as if they were catalog content.
The reference implementation discussed here is SonoTap. The transferable pattern is to use static generation as an integrity boundary: routes, translations, metadata, content, and client payloads are all artifacts that a build can count and reject.
Top comments (0)