<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Daniel Mercer</title>
    <description>The latest articles on DEV Community by Daniel Mercer (@danielmercer).</description>
    <link>https://dev.to/danielmercer</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4186020%2F5faef3a0-a3e9-4cce-8029-00c662efc658.png</url>
      <title>DEV Community: Daniel Mercer</title>
      <link>https://dev.to/danielmercer</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danielmercer"/>
    <language>en</language>
    <item>
      <title>Estimating moving costs without an address form: building a privacy-first quote calculator</title>
      <dc:creator>Daniel Mercer</dc:creator>
      <pubDate>Sat, 17 Oct 2026 06:15:42 +0000</pubDate>
      <link>https://dev.to/danielmercer/estimating-moving-costs-without-an-address-form-building-a-privacy-first-quote-calculator-17j3</link>
      <guid>https://dev.to/danielmercer/estimating-moving-costs-without-an-address-form-building-a-privacy-first-quote-calculator-17j3</guid>
      <description>&lt;p&gt;Most moving quote tools start the same way: enter your name, email, phone, current address, destination address — and then the calls begin. We built ours backwards. &lt;a href="https://myfastmovers.com" rel="noopener noreferrer"&gt;My Fast Movers&lt;/a&gt; estimates long-distance moving costs without asking who you are at all. Here's the engineering and product thinking behind that choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The insight: price is a function of route, not identity.&lt;/strong&gt; A long-distance move's cost is overwhelmingly determined by distance, shipment weight, and season. Your phone number contributes exactly zero predictive power. So we made the calculator's inputs just the route and a home size. Two dropdowns and a button, no account, no email gate. The estimate appears instantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where the data comes from.&lt;/strong&gt; We maintain cost models across 27,854 city-pair routes, built on Census migration data and carrier rate patterns. Each route page shows a price range rather than a fake-precise number, because a single number would be a lie — actual costs vary with weight, timing, and service level. Ranges are honest; precision is marketing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The hard part isn't math, it's trust.&lt;/strong&gt; A calculator nobody trusts is a dead widget. So every estimate shows its assumptions: what home size means in weight terms, how season shifts the range, what's excluded. There's a whole guide on &lt;a href="https://myfastmovers.com" rel="noopener noreferrer"&gt;how to read a moving quote line by line&lt;/a&gt; sitting next to the tool, because an estimate you can't interpret is just a number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Privacy as a feature, not a slogan.&lt;/strong&gt; The moving industry has a lead-generation problem: quote forms exist to harvest contact info for resale. We deliberately don't collect it. That costs us the obvious monetization path (selling leads), but it buys something rarer in this industry: users who actually finish the flow. Our completion rate is dramatically higher than the industry-standard gated form, because there's nothing to bounce off of.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technically, it's boring — on purpose.&lt;/strong&gt; The calculator is a lookup against precomputed route data plus a small adjustment model for season and home size. No ML, no real-time carrier API. Precomputed means it answers in milliseconds and works offline-ish behind a CDN. When your whole value proposition is "instant and private," latency is a trust signal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What the data taught us.&lt;/strong&gt; Two surprises from running this at scale: first, most users check 3–4 routes before settling — they're comparison shopping, and the tool's job is to be the neutral reference. Second, the cheapest question isn't "which mover" but "which month" — seasonality moves prices 20–30%, more than most carrier differences. We now surface that explicitly.&lt;/p&gt;

&lt;p&gt;If you're building any kind of estimator, the lesson generalizes: ask for the minimum inputs that determine the answer, show your work, and don't treat the user's contact info as the product. The &lt;a href="https://myfastmovers.com" rel="noopener noreferrer"&gt;moving cost calculator&lt;/a&gt; converts better than any gated form we could have built, precisely because it asks for nothing.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>privacy</category>
      <category>product</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Shipping a 56,000-game portal as a one-person operation: the stack that holds it together</title>
      <dc:creator>Daniel Mercer</dc:creator>
      <pubDate>Sat, 17 Oct 2026 06:14:57 +0000</pubDate>
      <link>https://dev.to/danielmercer/shipping-a-56000-game-portal-as-a-one-person-operation-the-stack-that-holds-it-together-jhd</link>
      <guid>https://dev.to/danielmercer/shipping-a-56000-game-portal-as-a-one-person-operation-the-stack-that-holds-it-together-jhd</guid>
      <description>&lt;p&gt;Running a site with fifty-six thousand games sounds like a team sport. In our case it's mostly one person, a cheap server, and a stack chosen for boring reliability over cleverness. Here's what actually holds &lt;a href="https://gameziko.com" rel="noopener noreferrer"&gt;GameZiko&lt;/a&gt; together, and the unglamorous decisions that matter most.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Static-first, dynamic where it counts.&lt;/strong&gt; The game pages are overwhelmingly static HTML generated at build time. Fifty-six thousand dynamic page renders per deploy would melt anything, so the catalog is pre-rendered: title, description, tags, embed, related games — all baked in. The dynamic parts (search, "recently played") are small API calls against a lean index. The rule: if it doesn't change per user, it shouldn't be computed per request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The game files are the easy part.&lt;/strong&gt; HTML5 games are self-contained bundles. The hard part is metadata quality — a game with a wrong title, a broken thumbnail, or a miscategorized tag is a dead page. We built a pipeline that validates each submission: does it load in a sandbox, does it actually respond to input, is the thumbnail the right aspect ratio. About a third of submissions fail validation and never ship. That filter is the product, honestly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Search is a solved problem if you stay humble.&lt;/strong&gt; We don't run Elasticsearch. A precomputed trigram index plus tag weighting handles "car games" and "2 player" fine at our scale. The moment you accept that your search doesn't need to be Google, the infrastructure gets ten times cheaper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Images are the silent budget killer.&lt;/strong&gt; Fifty-six thousand thumbnails will eat any hosting budget if you're careless. Ours are aggressively compressed WebP at fixed dimensions, served from a CDN with long cache headers. The page weight budget per game page is under 300KB excluding the game itself. On a site where most visitors are on phones or school Chromebooks, that discipline is the difference between playable and abandoned.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Caching everything, invalidating almost nothing.&lt;/strong&gt; Game pages change rarely, so cache TTLs are measured in days, not minutes. The only thing that busts cache is a metadata correction, and those go through a targeted purge. Stale-while-revalidate does the rest. Our origin server mostly serves the API and the build pipeline; the CDN does the real work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Monitoring that a solo dev will actually read.&lt;/strong&gt; We don't have dashboards — we have three alerts: error rate spike, deploy failure, and disk space. Everything else is a weekly report email. A monitoring setup you'll ignore is worse than a simple one you'll read.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I'd do differently.&lt;/strong&gt; I'd have built the validation pipeline first instead of bolting it on after the catalog passed ten thousand games. Cleaning ten thousand bad entries retroactively was the most miserable month of the project. If you're building any kind of user-submitted catalog: validate at ingest, or pay for it later with interest.&lt;/p&gt;

&lt;p&gt;The unsexy summary: pre-render everything, validate at the door, compress images like your budget depends on it (it does), and keep the dynamic surface tiny. &lt;a href="https://gameziko.com" rel="noopener noreferrer"&gt;GameZiko&lt;/a&gt; stays fast on a shoestring because the architecture refuses to do work it doesn't have to.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>indiedev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>We rebuilt 27,854 pages and lost 81% of our traffic — a redirect postmortem</title>
      <dc:creator>Daniel Mercer</dc:creator>
      <pubDate>Fri, 16 Oct 2026 13:44:20 +0000</pubDate>
      <link>https://dev.to/danielmercer/we-rebuilt-27854-pages-and-lost-81-of-our-traffic-a-redirect-postmortem-2252</link>
      <guid>https://dev.to/danielmercer/we-rebuilt-27854-pages-and-lost-81-of-our-traffic-a-redirect-postmortem-2252</guid>
      <description>&lt;p&gt;Last month we rebuilt &lt;a href="https://www.myfastmovers.com/" rel="noopener noreferrer"&gt;My Fast Movers&lt;/a&gt; — an independent moving-cost calculator with 27,854 city-to-city route pages — and migrated every route URL to a cleaner structure. Old URLs 301-redirected to new ones. Textbook migration. Then our Search Console impressions fell 81.7% in a week.&lt;br&gt;
Here's the honest postmortem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we did
&lt;/h2&gt;

&lt;p&gt;Old URL pattern: &lt;code&gt;/states/&amp;lt;state&amp;gt;/&amp;lt;city&amp;gt;/moving-from-X-to-Y/&lt;/code&gt;&lt;br&gt;
New URL pattern: &lt;code&gt;/popular-routes/&amp;lt;city&amp;gt;-to-&amp;lt;city&amp;gt;/&lt;/code&gt;&lt;br&gt;
Every old URL got a 301 to its new counterpart. No 404s. Sitemap updated and resubmitted (63,063 pages discovered). We expected a brief dip, then recovery.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually happened
&lt;/h2&gt;

&lt;p&gt;Google dropped the old URLs from the index fast — within days — but has been slow to index and rank the new ones. Two weeks post-migration: 9,049 pages indexed, 54,692 not indexed. The top reasons: "Crawled – currently not indexed" (38,712) and "Discovered – currently not indexed" (13,531). Our best route pages — the ones driving all the impressions — went from ranking to invisible while their replacements sat in the crawl queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we'd do differently
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Don't migrate all URLs at once.&lt;/strong&gt; We moved 27,854 pages in one deploy. A staged rollout — 10% of routes first, watching indexation — would have contained the damage.&lt;br&gt;
&lt;strong&gt;2. Treat "redirects are in place" as the start, not the end.&lt;/strong&gt; A 301 preserves link equity in theory. In practice, Google still has to recrawl, re-evaluate, and re-rank every new URL. For a site with tens of thousands of pages, that queue is long.&lt;br&gt;
&lt;strong&gt;3. Internal linking matters more than we thought.&lt;/strong&gt; Our new pages launched with thinner internal link graphs than the old ones had accumulated over years. We're now deliberately rebuilding internal links to priority routes.&lt;br&gt;
&lt;strong&gt;4. Watch the sitemap fetch status.&lt;/strong&gt; Two of our sub-sitemaps showed "Couldn't fetch" in Search Console while returning HTTP 200 externally — a Googlebot fetch-reliability issue that slowed discovery of exactly the pages we needed indexed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where we are now
&lt;/h2&gt;

&lt;p&gt;Redirects holding, no 404s, steadily requesting indexing on priority routes, and building fresh backlinks to the new URLs to speed up re-evaluation. Traffic hasn't recovered yet — I'm writing this mid-recovery, not after it.&lt;br&gt;
The lesson I'm taking: URL migrations on large sites aren't a redirect problem, they're an indexation-queue problem. Plan for the queue, not just the redirects.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I work on &lt;a href="https://www.myfastmovers.com/" rel="noopener noreferrer"&gt;My Fast Movers&lt;/a&gt;, a moving-cost calculator and route directory. If you've survived a large-scale URL migration, I'd love to hear how long your recovery took.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>seo</category>
      <category>webdev</category>
      <category>nextjs</category>
      <category>postmortem</category>
    </item>
    <item>
      <title>How we keep 56,000 browser games playable on low-end devices</title>
      <dc:creator>Daniel Mercer</dc:creator>
      <pubDate>Fri, 16 Oct 2026 13:42:29 +0000</pubDate>
      <link>https://dev.to/danielmercer/how-we-keep-56000-browser-games-playable-on-low-end-devices-3pd1</link>
      <guid>https://dev.to/danielmercer/how-we-keep-56000-browser-games-playable-on-low-end-devices-3pd1</guid>
      <description>&lt;p&gt;Most of our players aren't on gaming rigs. They're on school Chromebooks, five-year-old Android phones, and library computers. When you run a free browser-games portal with 56,000+ HTML5 games (&lt;a href="https://gameziko.com" rel="noopener noreferrer"&gt;Gameziko&lt;/a&gt;), performance isn't a nice-to-have — it's the product. Here's what actually moved the needle for us.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Never load the game before the click
&lt;/h2&gt;

&lt;p&gt;The biggest win was also the simplest: the game iframe doesn't exist until the player presses play. The game page loads a lightweight thumbnail, title, and description first — usually under 100KB. Only on the play click do we inject the iframe. This cut our median page weight by ~80% and made game pages feel instant even on 3G.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Thumbnails are the hidden budget killer
&lt;/h2&gt;

&lt;p&gt;56,000 games means 56,000 thumbnails. We serve them as WebP with JPEG fallbacks, lazy-load everything below the fold with &lt;code&gt;loading="lazy"&lt;/code&gt;, and cap thumbnail dimensions at the largest size we ever render (no 1200px images squeezed into 200px cards). Total image bytes per listing page dropped by two-thirds.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. One shared game shell, not 56,000 snowflakes
&lt;/h2&gt;

&lt;p&gt;Every game page uses the same shell: same header, same player chrome, same ad slots. That means the shell's CSS and JS are cached after the first pageview, and subsequent game pages are mostly just JSON. Consistency also means one place to fix bugs instead of thousands.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Kill third-party scripts ruthlessly
&lt;/h2&gt;

&lt;p&gt;We audited every third-party script on game pages and cut the list to the bone. Each script gets a simple test: does it earn its kilobytes in revenue or functionality? Anything that failed is gone. Ad scripts are the worst offenders — we load them async and never let them block the game iframe.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Measure on the worst device you can find
&lt;/h2&gt;

&lt;p&gt;Our performance budget is tested on a 2019 Chromebook, not a MacBook Pro. If a game page hits interactive in under 3 seconds there, it ships. Real-user metrics confirmed what lab tests suggested: bounce rate on game pages tracks almost linearly with time-to-play-button.&lt;/p&gt;

&lt;h2&gt;
  
  
  The boring truth
&lt;/h2&gt;

&lt;p&gt;There's no magic here — no service worker wizardry, no edge computing. It's lazy loading, small images, cached shells, and fewer scripts. The unglamorous fundamentals, applied 56,000 times over.&lt;br&gt;
If you're building anything that serves a lot of lightweight pages to a lot of underpowered devices, start with the click-to-load pattern. It's the highest-leverage change we made, and it took an afternoon.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I run &lt;a href="https://gameziko.com" rel="noopener noreferrer"&gt;Gameziko&lt;/a&gt;, a free portal with 56,000+ HTML5 games that need no download or signup. Happy to answer questions about running game portals at scale.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webperf</category>
      <category>javascript</category>
      <category>html</category>
      <category>performance</category>
    </item>
    <item>
      <title>How we priced 27,854 moving routes using Census migration data</title>
      <dc:creator>Daniel Mercer</dc:creator>
      <pubDate>Fri, 16 Oct 2026 06:11:15 +0000</pubDate>
      <link>https://dev.to/danielmercer/how-we-priced-27854-moving-routes-using-census-migration-data-1372</link>
      <guid>https://dev.to/danielmercer/how-we-priced-27854-moving-routes-using-census-migration-data-1372</guid>
      <description>&lt;p&gt;Last year I shipped a side project: a long-distance moving cost calculator plus a directory of city-to-city moving routes. The calculator UI took a weekend. The hard part — the part this post is about — was producing believable prices for 27,854 city pairs without hand-entering a single one.&lt;/p&gt;

&lt;p&gt;Here's how the pricing model works, where the data comes from, and the engineering tradeoffs that keep 27,854 pages alive.&lt;/p&gt;

&lt;h2&gt;
  
  
  The demand signal: Census migration flows
&lt;/h2&gt;

&lt;p&gt;The US Census Bureau publishes county-to-county migration flow tables (from the American Community Survey). They tell you, roughly, how many people moved from county X to county Y in a given year. What they don't tell you is price — but price pages for routes nobody travels are dead weight.&lt;/p&gt;

&lt;p&gt;So the migration data became the route selector. I aggregated county-level flows up to city level, kept pairs with meaningful annual migration volume, and that became the route list: 27,854 city pairs, each carrying an estimated mover count.&lt;/p&gt;

&lt;p&gt;Two practical gotchas. First, the data lags reality by about two years — the most recent release describes the world as it was, not as it is. That's fine for route selection; pricing inputs, which move faster, are handled separately. Second, small flows are suppressed for privacy. I treat suppressed pairs as "unknown demand" rather than zero, so thin but real routes don't get dropped silently.&lt;/p&gt;

&lt;h2&gt;
  
  
  The cost model: four levers
&lt;/h2&gt;

&lt;p&gt;Every route price comes from one estimator with four weighted inputs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Distance.&lt;/strong&gt; Base cost is roughly linear in miles. I compute great-circle distance between city centroids, then apply a circuity factor (~1.2×) because roads aren't straight lines. That factor is calibrated against published lane rates from FMCSA carrier data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Home size.&lt;/strong&gt; A studio ships around 1,800 lbs; a four-bedroom house ships 10,000+ lbs. Weight determines truck space, and truck space is what you're really buying. The calculator lets users pick their home size; the route pages show ranges across typical sizes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Season.&lt;/strong&gt; Summer moves run 20–30% above winter moves in this industry. A seasonal multiplier handles peak (May–September) vs. off-peak, derived from historical rate seasonality rather than vibes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Move type.&lt;/strong&gt; Full-service movers, moving containers, and DIY truck rentals have completely different cost structures. The estimator outputs a range per move type instead of pretending one number fits all.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Roughly: base_rate(miles, weight) × seasonal_factor × move_type_factor, then rounded into a range. Deliberately a range — false precision ("$4,213.77") is worse than honesty ("$3,800–$4,600").&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the model admits it's guessing
&lt;/h2&gt;

&lt;p&gt;A model is not a quote. The carrier, fuel surcharges, stairs, long carries, exact dates — all move the real number. The site says this plainly and I'll say it here: these are planning figures. The value is comparative. Knowing that a 2-bedroom move from Austin to Denver costs roughly half of a 4-bedroom move from New York to Los Angeles is genuinely useful even when neither endpoint is exact.&lt;/p&gt;

&lt;h2&gt;
  
  
  The engineering: static generation at 27,854 pages
&lt;/h2&gt;

&lt;p&gt;The route pages are fully static. The pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Python/pandas data job ingests the Census migration tables, aggregates to city pairs, and joins the pricing model output — one JSON record per route.&lt;/li&gt;
&lt;li&gt;A static site generator builds one page per city pair at build time. 27,854 pages sounds heavy, but each is a small HTML file; the full build takes minutes and the output sits on a CDN with trivial cache behavior.&lt;/li&gt;
&lt;li&gt;Sitemaps are sharded (the 50k-URL-per-sitemap limit means one sitemap index covers everything) and submitted to Search Console.&lt;/li&gt;
&lt;li&gt;Internal linking is structural, not an afterthought: every city page links to its top routes, every route page links back to both cities. That's 27,854 pages with a real link graph and zero orphans.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why static instead of one dynamic template with query params? Crawlability — search engines index real URLs far more reliably — and speed: no database on the hot path, just a file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keeping the data fresh
&lt;/h2&gt;

&lt;p&gt;Inputs drift on different schedules, so the pipeline splits them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stable:&lt;/strong&gt; city coordinates, the route demand list (rebuilt annually when new Census data lands).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Volatile:&lt;/strong&gt; fuel prices (weekly), seasonal multipliers, FMCSA carrier snapshots.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A weekly job recomputes prices and triggers incremental rebuilds of the affected pages. The annual Census refresh rebuilds the route list itself. Separating the two keeps a fuel-price spike from requiring a full 27,854-page rebuild.&lt;/p&gt;

&lt;p&gt;The biggest lesson from the whole build: the model's credibility comes from named, real inputs. "We weight distance, home size, season, and move type — here's the data behind each" is checkable. "Our proprietary algorithm computes your perfect price" is not. For a calculator site, trust is the product.&lt;/p&gt;

&lt;p&gt;You can poke at the live calculator at &lt;a href="https://myfastmovers.com" rel="noopener noreferrer"&gt;myfastmovers.com&lt;/a&gt; — one link, because that's the only place it belongs.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>datascience</category>
      <category>seo</category>
    </item>
    <item>
      <title>Running a 56,000-game browser portal: what breaks at scale (and what doesn't)</title>
      <dc:creator>Daniel Mercer</dc:creator>
      <pubDate>Thu, 15 Oct 2026 06:15:14 +0000</pubDate>
      <link>https://dev.to/danielmercer/running-a-56000-game-browser-portal-what-breaks-at-scale-and-what-doesnt-5gl2</link>
      <guid>https://dev.to/danielmercer/running-a-56000-game-browser-portal-what-breaks-at-scale-and-what-doesnt-5gl2</guid>
      <description>&lt;p&gt;I run Gameziko, a free browser-games portal with more than 56,000 HTML5 games — no downloads, no sign-ups, just click and play. People hear "56,000 games" and assume the hard part is collecting that many games. It isn't. The hard parts are trust, categorization, and keeping 56,000 pages fast. Here's what we learned.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Never trust a third-party game file
&lt;/h2&gt;

&lt;p&gt;Every game on the portal runs inside an iframe with a restrictive sandbox attribute. We allow scripts (games need them) but we do not allow top-navigation or pop-ups by default — a game should never be able to redirect your whole tab or spawn pop-up windows. On top of that, a Content-Security-Policy on our pages blocks the usual exfiltration tricks.&lt;/p&gt;

&lt;p&gt;New submissions get screened before they go live: we check that the file is actually an HTML5 game, that it loads without errors, and that it doesn't phone home to anything suspicious. This is the boring work nobody writes about, but one malicious upload would destroy user trust overnight.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Lazy-load everything, or the page count kills you
&lt;/h2&gt;

&lt;p&gt;A category page listing hundreds of games can't load hundreds of iframes. Each game card renders a thumbnail (WebP, with srcset for retina screens) and a title. The actual game iframe is created only when the user clicks "play". This keeps category pages loading in about a second on a normal connection, which matters a lot — most of our traffic is casual players on phones, and they will not wait.&lt;/p&gt;

&lt;p&gt;We also cap how many games render per page and paginate aggressively. Infinite scroll looks nice in demos; it tanks real-world performance on low-end devices.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The categorization problem is a data problem
&lt;/h2&gt;

&lt;p&gt;56,000 games with messy, inconsistent tags is a mess. Our approach is deliberately simple: a rules-based tagger that maps incoming metadata to a fixed category set (action, puzzle, racing, io, sports, and so on), plus manual curation for the front-page categories. Fancy ML classification was tempting, but a deterministic rule set is debuggable — when a chess game lands in "racing", we can find the rule that did it and fix it.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Games die — plan for it
&lt;/h2&gt;

&lt;p&gt;Game files hosted by third parties go dead: domains expire, developers pull files, hosting breaks. We run scheduled availability checks and delist games that fail repeatedly. Delist, not delete — a game can come back. If a game fails to load, the player sees related working games instead of an error page. Every dead end on a games site is a lost visitor; turning it into a recommendation recovers most of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The SEO lesson nobody wants to hear
&lt;/h2&gt;

&lt;p&gt;Template text doesn't rank. 56,000 pages with near-duplicate descriptions are treated as near-duplicates by search engines. We write or curate unique short descriptions for our most important games and categories, and we interlink heavily between related games. That internal link graph is doing more for us than any backlink campaign ever could.&lt;/p&gt;

&lt;p&gt;The portal itself is live at Gameziko — &lt;a href="https://gameziko.com" rel="noopener noreferrer"&gt;https://gameziko.com&lt;/a&gt; — free browser games, no downloads, no sign-up. If you've run anything with a huge page count, I'd genuinely like to hear what broke for you.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>seo</category>
      <category>startup</category>
    </item>
  </channel>
</rss>
