<?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: qqaa27485781</title>
    <description>The latest articles on DEV Community by qqaa27485781 (@qqaa27485781).</description>
    <link>https://dev.to/qqaa27485781</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%2F4193081%2Fa314d38f-0767-4264-8084-273464d6b2d3.png</url>
      <title>DEV Community: qqaa27485781</title>
      <link>https://dev.to/qqaa27485781</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/qqaa27485781"/>
    <language>en</language>
    <item>
      <title>What broke when my game catalog passed 2,700 pages</title>
      <dc:creator>qqaa27485781</dc:creator>
      <pubDate>Mon, 19 Oct 2026 03:24:58 +0000</pubDate>
      <link>https://dev.to/qqaa27485781/what-broke-when-my-game-catalog-passed-2700-pages-2791</link>
      <guid>https://dev.to/qqaa27485781/what-broke-when-my-game-catalog-passed-2700-pages-2791</guid>
      <description>&lt;p&gt;I run &lt;a href="https://targaryenquiz.xyz" rel="noopener noreferrer"&gt;TapArcade&lt;/a&gt;, a free browser-game arcade. It's a&lt;br&gt;
PHP/MySQL app behind Caddy in Docker, fronted by Cloudflare, on the smallest VPS&lt;br&gt;
tier I could find. At 2,700+ game pages, the things that broke were not the things&lt;br&gt;
I expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PHP app served by Caddy in a container&lt;/li&gt;
&lt;li&gt;Caddy reverse-proxies to the app container on port 80&lt;/li&gt;
&lt;li&gt;Cloudflare in front, origin only speaks HTTP&lt;/li&gt;
&lt;li&gt;Sitemap generated on demand: home + 19 category pages + 2,700 game pages = 2,721 URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing exotic. The interesting part is what happens at that page count.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Static files in the web root are load-bearing
&lt;/h2&gt;

&lt;p&gt;Search engines verify site ownership by asking you to drop a file in the web root&lt;br&gt;
and then fetching it. &lt;code&gt;google&amp;lt;hash&amp;gt;.html&lt;/code&gt;, &lt;code&gt;BingSiteAuth.xml&lt;/code&gt;, whatever.&lt;/p&gt;

&lt;p&gt;My deploy strategy was "build into a staging directory, then rsync over the live&lt;br&gt;
directory". That works right up until the day someone adds &lt;code&gt;--delete&lt;/code&gt; and wipes&lt;br&gt;
every verification file in the root, because they were never in the build output.&lt;/p&gt;

&lt;p&gt;Two verification files went 404 and I spent an evening debugging a sitemap problem&lt;br&gt;
that was actually an ownership-verification problem. The fix was boring: exclude&lt;br&gt;
&lt;code&gt;google*.html&lt;/code&gt; and &lt;code&gt;BingSiteAuth.xml&lt;/code&gt; from the delete, and assert they still exist&lt;br&gt;
in the staging directory before the swap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; if a file's only job is to sit in the web root and be fetched by a bot,&lt;br&gt;
it belongs in your deploy whitelist, not your build output.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. A sitemap that is served correctly can still never be fetched
&lt;/h2&gt;

&lt;p&gt;The sitemap returned 200, &lt;code&gt;application/xml&lt;/code&gt;, correct byte count, valid XML, 2,721&lt;br&gt;
unique URLs, no illegal characters. I checked it fifteen times through Cloudflare&lt;br&gt;
and it never once failed.&lt;/p&gt;

&lt;p&gt;The search console still said "couldn't fetch".&lt;/p&gt;

&lt;p&gt;Server logs were the answer: the crawler had requested &lt;code&gt;robots.txt&lt;/code&gt;, &lt;code&gt;/&lt;/code&gt;, and&lt;br&gt;
&lt;code&gt;favicon.ico&lt;/code&gt; — and never asked for the sitemap at all. It wasn't a serving&lt;br&gt;
problem. The property was never verified, so nothing downstream mattered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; "couldn't fetch" in a search console is not proof that a fetch was&lt;br&gt;
attempted. Check the access log for the sitemap path before you touch your server&lt;br&gt;
config.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Category pages need to be different from each other
&lt;/h2&gt;

&lt;p&gt;At 2,700 games, category pages are the only realistic entry points — nobody&lt;br&gt;
lands on game #2,431 from search. Early on, every category page was the same&lt;br&gt;
template with a different grid, which is a thin-content problem at scale.&lt;/p&gt;

&lt;p&gt;What helped: each category page got a short editorial intro explaining what the&lt;br&gt;
category actually is and who it's for, plus the grid. Same template, different&lt;br&gt;
copy per category. It doesn't sound like much. It's the difference between 19&lt;br&gt;
duplicate pages and 19 real pages.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. 2,700 pages is where "just add a CDN" stops helping
&lt;/h2&gt;

&lt;p&gt;Cloudflare fixes latency. It does not fix crawl budget, and it does not fix the&lt;br&gt;
fact that a catalog page with no unique text is a catalog page with no unique text.&lt;/p&gt;

&lt;p&gt;The wins at this size came from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one sitemap, regenerated when the catalog changes&lt;/li&gt;
&lt;li&gt;category intros that are actually about the category&lt;/li&gt;
&lt;li&gt;per-game pages that say something specific (controls, platform, what it is)&lt;/li&gt;
&lt;li&gt;not shipping a page until it has content worth indexing&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;None of this is clever. It's the unglamorous part of running a catalog site: make&lt;br&gt;
the verification files survive deploys, make sure the sitemap is actually being&lt;br&gt;
requested, and don't ship 19 pages that are the same page.&lt;/p&gt;

&lt;p&gt;If you want to see the result: &lt;a href="https://targaryenquiz.xyz" rel="noopener noreferrer"&gt;TapArcade&lt;/a&gt; — 2,700+&lt;br&gt;
free browser games across 19 categories, no download, no account.&lt;/p&gt;

</description>
      <category>webdevphpdockerseo</category>
    </item>
  </channel>
</rss>
