DEV Community

anil34anil
anil34anil

Posted on

How I Built a 6,000-Game Web Portal with Next.js 14 (and Kept Hosting Nearly Free)

I recently launched OYNAVA, a free HTML5 game portal with 6,000+ games. Here's the architecture, the cost traps I hit in production, and how I fixed them. If you're building a content-heavy site, a few of these will probably bite you too.

The stack

  • Next.js 14 (App Router) + TypeScript + Tailwind
  • Game feeds: GameMonetize, GamePix, GameDistribution (merged + normalized into one catalog)
  • Redis for shared likes/plays counters
  • Hosting: Netlify (after migrating off Vercel — more on that below)

Lesson 1: dynamic SSR quietly eats your free tier

My i18n read the locale from headers(), which forced every page to render dynamically (ƒ). Every request hit the origin, and Vercel's "Fast Origin Transfer" quota (10 GB) filled in days.

Fix: make content pages static/ISR with export const revalidate = 3600 + generateStaticParams, and move locale to URL prefixes with client-side chrome translation. Origin transfer dropped to almost zero.

Lesson 2: the client-side N+1

Every game card fetched its like-count individually — ~180 requests on a single homepage view. That maxed out serverless CPU.

Fix: an 80 ms client-side batcher that collapses them into one /api/likes/batch call. Then I went further and removed live counts from cards completely — they only matter on the detail page.

Lesson 3: node-redis + a full database = silent failure

Two things bit me at once:

  1. Persistent TCP connections (node-redis) are fragile in serverless.
  2. My free 30 MB Redis silently filled up. With noeviction, writes were rejected but reads looked fine — so likes "worked" in dev and silently failed in prod.

Fix: TTLs on cache keys (a translation cache was the culprit), plus an admin cleanup endpoint. Lesson: always give cache keys a TTL.

Programmatic SEO at scale

  • ~60 curated collection pages (/en-iyi-araba-oyunlari, /ates-ve-su-oyunlari …)
  • ~480 tag landing pages generated from game metadata, with a min-games threshold and noindex for thin ones
  • VideoGame / FAQPage / ItemList JSON-LD everywhere
  • One sitemap with ~7,000 URLs

Lesson 4: don't advertise duplicate hreflang

After I reverted server-side translation, all locale URLs served identical Turkish content — but I was still emitting hreflang for 8 languages. Google discovered 8× duplicate URLs and wasted crawl budget on them instead of indexing the real pages. Removing hreflang (canonical only) refocused the crawler.

PWA → Google Play

The site is a PWA (manifest + service worker), packaged as a TWA with PWABuilder and shipped to Google Play. One codebase, and web deploys update the app instantly — no store release needed.


If you want to poke at the result, it's live at oynava.com. Happy to answer questions about any of these in the comments. 🎮

Top comments (0)