<?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: Chen Tao</title>
    <description>The latest articles on DEV Community by Chen Tao (@cksmct).</description>
    <link>https://dev.to/cksmct</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%2F3992839%2F2d73520a-8219-411e-b15f-811a183a8b9a.jpg</url>
      <title>DEV Community: Chen Tao</title>
      <link>https://dev.to/cksmct</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cksmct"/>
    <language>en</language>
    <item>
      <title>Building a Roblox Game Guide with Next.js 16, Tailwind CSS v4, and a Single-Source-of-Truth Routing System</title>
      <dc:creator>Chen Tao</dc:creator>
      <pubDate>Thu, 30 Jul 2026 03:16:10 +0000</pubDate>
      <link>https://dev.to/cksmct/building-a-roblox-game-guide-with-nextjs-16-tailwind-css-v4-and-a-single-source-of-truth-routing-92h</link>
      <guid>https://dev.to/cksmct/building-a-roblox-game-guide-with-nextjs-16-tailwind-css-v4-and-a-single-source-of-truth-routing-92h</guid>
      <description>&lt;p&gt;I recently built &lt;a href="https://catchabrainrot.site" rel="noopener noreferrer"&gt;Catch a Brainrot Guide&lt;/a&gt;, a fan-made resource hub for a Roblox creature-collection RPG. Here's a walkthrough of the tech stack and some architectural decisions that made the project interesting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 16&lt;/strong&gt; (App Router, static export)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;React 19&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS v4&lt;/strong&gt; — the new &lt;code&gt;@theme&lt;/code&gt; directive means no more &lt;code&gt;tailwind.config.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TypeScript 5.7&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Pages&lt;/strong&gt; for deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Single Source of Truth for Routes
&lt;/h2&gt;

&lt;p&gt;All 20+ page routes live in one file: &lt;code&gt;src/lib/routes.ts&lt;/code&gt;. Every route is typed with its path, label, sitemap priority, changefreq, and nav/footer placement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ROUTES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RouteDef&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/tier-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tier List&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;changefreq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;daily&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;brainrots&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;footer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quick&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/guide/how-to-play&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;How to Play&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;changefreq&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;weekly&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;nav&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;guides&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="c1"&gt;// ... 20+ pages&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Navbar, Footer, and &lt;code&gt;sitemap.ts&lt;/code&gt; all import from this single array. Adding a new page means one entry in &lt;code&gt;routes.ts&lt;/code&gt; — everything else syncs automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// sitemap.ts — auto-syncs from ROUTES&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sitemap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;ROUTES&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;GAME&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;GAME&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;changeFrequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changefreq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tailwind CSS v4: Bye-Bye tailwind.config.ts
&lt;/h2&gt;

&lt;p&gt;Tailwind v4 moves configuration into native CSS via &lt;code&gt;@theme&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="k"&gt;@import&lt;/span&gt; &lt;span class="s1"&gt;"tailwindcss"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;@theme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;--color-background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#0a0a0f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-foreground&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#f1f5f9&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-primary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#8b5cf6&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-card&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;#14141f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;--color-glass-border&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rgba&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;255&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0.08&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SEO Architecture (Three Layers)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Global Metadata (layout.tsx)
&lt;/h3&gt;

&lt;p&gt;Title templates, Open Graph, Twitter cards, robots directives, and canonical URL are set once in the root layout.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;GAME&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; - Roblox Game Guide, Codes &amp;amp; Wiki`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`%s | &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;GAME&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;robots&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;follow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;alternates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;canonical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GAME&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;domain&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Page-Level JSON-LD Schemas
&lt;/h3&gt;

&lt;p&gt;Every page injects context-specific structured data — &lt;code&gt;BreadcrumbList&lt;/code&gt;, &lt;code&gt;FAQPage&lt;/code&gt;, or &lt;code&gt;Article&lt;/code&gt; schemas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;breadcrumbSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;generateBreadcrumbSchema&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Home&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Tier List&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/tier-list&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. IndexNow Auto-Submission
&lt;/h3&gt;

&lt;p&gt;After each build, a script parses &lt;code&gt;sitemap.xml&lt;/code&gt; and pushes all URLs to the IndexNow API (accepted by Bing, Yandex, Naver). No registration required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;catchabrainrot.site&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;your-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;urlList&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;allUrls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://api.indexnow.org/indexnow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  YouTube Embed: Facade Pattern
&lt;/h2&gt;

&lt;p&gt;Instead of loading a 1MB+ YouTube iframe on page load, I use a facade — a static thumbnail with a play button that swaps to the real iframe only on click.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;YouTubeEmbed&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="nx"&gt;Props&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;loaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setLoaded&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;thumbnail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`https://img.youtube.com/vi/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/mqdefault.jpg`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;loaded&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;iframe&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`https://www.youtube.com/embed/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;videoId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;?autoplay=1`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt; &lt;span class="na"&gt;onClick&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setLoaded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;thumbnail&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;loading&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"lazy"&lt;/span&gt; &lt;span class="na"&gt;fetchPriority&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"low"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PlayIcon&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The "Honest Codes" Pattern
&lt;/h2&gt;

&lt;p&gt;A common SEO play in Roblox guide sites is to fabricate fake codes to trap search traffic. I went the opposite direction: the /codes page transparently states the game has no code redemption system yet, and redirects users toward the official Discord for updates. It ranks well and earns trust — a better long-term play than fake codes.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-Assisted Development
&lt;/h2&gt;

&lt;p&gt;The repo includes an AGENTS.md that defines an SEO workflow: when a keyword research report lands, the agent analyzes priorities, checks existing coverage, generates new pages, updates sitemap/nav, runs an SEO audit, builds, and logs results. This turns keyword research into a semi-automated pipeline from idea to deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deployment
&lt;/h2&gt;

&lt;p&gt;Static export to Cloudflare Pages, with IndexNow submission running post-build.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single source of truth&lt;/strong&gt; for routes eliminates maintenance drift&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured SEO from day one&lt;/strong&gt; beats retrofitting later&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honesty as a content strategy&lt;/strong&gt; builds trust and ranks better than fabricated content&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full site is live at &lt;a href="https://catchabrainrot.site" rel="noopener noreferrer"&gt;catchabrainrot.site&lt;/a&gt;. Questions or thoughts? Drop a comment below.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>gamedev</category>
      <category>roblox</category>
    </item>
    <item>
      <title>Building a Lightning-Fast Roblox Gaming Guide Site with Next.js 16 SSG &amp; IndexNow Automation</title>
      <dc:creator>Chen Tao</dc:creator>
      <pubDate>Sat, 25 Jul 2026 10:59:34 +0000</pubDate>
      <link>https://dev.to/cksmct/building-a-lightning-fast-roblox-gaming-guide-site-with-nextjs-16-ssg-indexnow-automation-eel</link>
      <guid>https://dev.to/cksmct/building-a-lightning-fast-roblox-gaming-guide-site-with-nextjs-16-ssg-indexnow-automation-eel</guid>
      <description>&lt;p&gt;Building a specialized wiki or gaming guide platform requires balancing &lt;strong&gt;speed&lt;/strong&gt;, &lt;strong&gt;SEO indexing efficiency&lt;/strong&gt;, and &lt;strong&gt;mobile-first UX&lt;/strong&gt;. In this technical case study, I'll walk through how we engineered &lt;a href="https://animalhospital.blog" rel="noopener noreferrer"&gt;Animal Hospital Guide &amp;amp; Tools&lt;/a&gt;, a high-performance gaming wiki built for Roblox horror game enthusiasts, compiled into &lt;strong&gt;50+ pure static HTML routes&lt;/strong&gt; using Next.js 16 Turbopack and automated search engine indexation.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Why SSG Over SSR for Gaming Guides
&lt;/h2&gt;

&lt;p&gt;Gaming guide sites experience extreme traffic spikes whenever new game updates or active codes are released. Server-Side Rendering (SSR) adds CPU overhead and latency during peak traffic.&lt;/p&gt;

&lt;p&gt;By utilizing &lt;strong&gt;Static Site Generation (SSG)&lt;/strong&gt; with Next.js App Router, every page (from the &lt;a href="https://animalhospital.blog/codes" rel="noopener noreferrer"&gt;Roblox Animal Hospital Active Codes Tracker&lt;/a&gt; to the &lt;a href="https://animalhospital.blog/tier-list" rel="noopener noreferrer"&gt;Animal Hospital Class Power Rankings Tier List&lt;/a&gt;) is pre-rendered at build time into optimized HTML files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/sitemap.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;gameConfig&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/data/game.config.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sitemap&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;MetadataRoute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sitemap&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;baseUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://animalhospital.blog&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;currentDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;gameConfig&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;lastModified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;changeFrequency&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;changefreq&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  2. Instant Search Engine Indexation via IndexNow API
&lt;/h2&gt;

&lt;p&gt;For gaming hubs, search engine indexing speed is critical. Instead of waiting days for search crawlers to discover new updates, we built a Python automation pipeline using the &lt;strong&gt;IndexNow Protocol&lt;/strong&gt; (supported by Microsoft Bing, Yandex, and Seznam).&lt;/p&gt;

&lt;p&gt;Whenever static pages are exported, our build script broadcasts the URL matrix directly to IndexNow endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;urllib.request&lt;/span&gt;

&lt;span class="n"&gt;HOST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;animalhospital.blog&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a8d9e2f47c1b569302e1f487b39c05e1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;INDEXNOW_ENDPOINT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://yandex.com/indexnow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;submit_urls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;host&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;keyLocation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;HOST&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;urlList&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;INDEXNOW_ENDPOINT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application/json; charset=utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;urllib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;urlopen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🚀 Successfully broadcasted 50+ URLs to IndexNow network!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This guarantees that pages like the interactive &lt;a href="https://animalhospital.blog/anomalies" rel="noopener noreferrer"&gt;Animal Hospital Anomaly Finder &amp;amp; Walkthrough&lt;/a&gt; get indexed within minutes of deployment.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Mobile-First UI Defenses: Zero Scrollbar Eraser
&lt;/h2&gt;

&lt;p&gt;A common flaw in mobile WebApp navigation is native browser scrollbars cluttering top navigation bars. To solve this, we enforced CSS scrollbar erasure while preserving smooth touch panning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* In globals.css */&lt;/span&gt;
&lt;span class="nc"&gt;.no-scrollbar&lt;/span&gt;&lt;span class="nd"&gt;::-webkit-scrollbar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nc"&gt;.no-scrollbar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;-ms-overflow-style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="py"&gt;scrollbar-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Paired with a responsive 2-column mobile drawer overlay, users on mobile devices experience zero layout shift (Zero CLS) and crisp navigation.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Key Takeaways &amp;amp; Stack Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Framework&lt;/strong&gt;: Next.js 16 (App Router + Turbopack)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling&lt;/strong&gt;: Tailwind CSS v4 + Vanilla Glassmorphism&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hosting&lt;/strong&gt;: Cloudflare Pages (Zero-Cold Start Global CDN)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEO&lt;/strong&gt;: Automated IndexNow Push + Schema JSON-LD Injection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check out the live platform at &lt;a href="https://animalhospital.blog" rel="noopener noreferrer"&gt;Animal Hospital Guide&lt;/a&gt; to see the performance and static pre-rendering in action!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>seo</category>
      <category>react</category>
    </item>
    <item>
      <title>How I Built a Game Wiki with 38 Pages Using Next.js Static Export</title>
      <dc:creator>Chen Tao</dc:creator>
      <pubDate>Wed, 22 Jul 2026 02:34:27 +0000</pubDate>
      <link>https://dev.to/cksmct/how-i-built-a-game-wiki-with-38-pages-using-nextjs-static-export-4c6b</link>
      <guid>https://dev.to/cksmct/how-i-built-a-game-wiki-with-38-pages-using-nextjs-static-export-4c6b</guid>
      <description>&lt;p&gt;I recently built &lt;strong&gt;Gakuran Guide&lt;/strong&gt; (gakuranguide.wiki), a comprehensive wiki for a Roblox fighting game. It has 38 static pages, rich SEO metadata, and builds in under 30 seconds. Here's how.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Next.js 14&lt;/strong&gt; with App Router&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Static Export&lt;/strong&gt; (&lt;code&gt;output: 'export'&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; for styling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON-LD Structured Data&lt;/strong&gt; for every guide page&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Sitemap&lt;/strong&gt; with auto-detection of all routes&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Static Export?
&lt;/h3&gt;

&lt;p&gt;The Gakuran game data changes weekly, but I rebuild weekly — making SSR/ISR overkill. Static export means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Files served from a CDN (Cloudflare Pages — free)&lt;/li&gt;
&lt;li&gt;No server costs. Zero.&lt;/li&gt;
&lt;li&gt;100/100 Lighthouse scores&lt;/li&gt;
&lt;li&gt;No hydration issues&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  JSON-LD: Rich Results Without SSR
&lt;/h3&gt;

&lt;p&gt;Every guide page has structured data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;GuidePage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buildDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLocaleDateString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;short&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;script&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"application/ld+json"&lt;/span&gt;
        &lt;span class="na"&gt;dangerouslySetInnerHTML&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;__html&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@context&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://schema.org&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Article&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;headline&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Fighting Styles Tier List&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dateModified&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;buildDate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="c1"&gt;// ...&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* page content */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since &lt;code&gt;new Date()&lt;/code&gt; runs at build time for static export, the date is always fresh without any runtime overhead.&lt;/p&gt;

&lt;h3&gt;
  
  
  Auto-Updating Build Dates
&lt;/h3&gt;

&lt;p&gt;Every page has an "Updated [date]" badge. No cron, no API — it's just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buildDate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLocaleDateString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;en-US&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;month&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;short&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;day&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;year&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;numeric&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rebuild → new date. Simple.&lt;/p&gt;

&lt;h3&gt;
  
  
  SEO Results (3 weeks in)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;First-page Google ranking for "gakuran tier list"&lt;/li&gt;
&lt;li&gt;Featured snippet for "gakuran codes"&lt;/li&gt;
&lt;li&gt;All 38 pages indexed within 48 hours&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Key Takeaways
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Static export is underrated.&lt;/strong&gt; For content sites, it's faster and cheaper than SSR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JSON-LD works perfectly with static export.&lt;/strong&gt; Just bake it in at build time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental improvement works.&lt;/strong&gt; Ship the first 10 pages, then add more&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Footer architecture matters.&lt;/strong&gt; Every page should be reachable via footer links — we fixed this mid-project&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Full site: &lt;a href="https://gakuranguide.wiki/" rel="noopener noreferrer"&gt;gakuranguide.wiki&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Happy to answer questions about building game wikis with Next.js!&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>webdev</category>
      <category>ai</category>
      <category>seo</category>
    </item>
    <item>
      <title>How I Built a Multi-Dimensional Search Engine for East Asian Political Data (And Survived the Fragmentation)</title>
      <dc:creator>Chen Tao</dc:creator>
      <pubDate>Sat, 18 Jul 2026 14:52:20 +0000</pubDate>
      <link>https://dev.to/cksmct/how-i-built-a-multi-dimensional-search-engine-for-east-asian-political-data-and-survived-the-47p8</link>
      <guid>https://dev.to/cksmct/how-i-built-a-multi-dimensional-search-engine-for-east-asian-political-data-and-survived-the-47p8</guid>
      <description>&lt;p&gt;If you have ever tried to aggregate or analyze geopolitical data from Northeast Asia—specifically Japan and South Korea—you already know the painful truth: the data is heavily siloed, structured inconsistently, and trapped behind deep language barriers.&lt;/p&gt;

&lt;p&gt;While working as a current affairs analyst, I grew tired of manually cross-referencing outdated spreadsheets, clunky government PDFs, and fragmented official portals just to find simple statistics (e.g., "How many female lawmakers in the Japanese Diet have served fewer than three terms?").&lt;/p&gt;

&lt;p&gt;To solve this, I built &lt;a href="https://eastasiainsights.com/" rel="noopener noreferrer"&gt;East Asia Insights&lt;/a&gt;, a free, localized, and multi-dimensional lookup directory for Japanese and South Korean parliamentarians.&lt;/p&gt;

&lt;p&gt;Here is a look behind the curtain at the data engineering challenges I faced and how I tackled them.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Core Challenge: Data Fragmentation &amp;amp; Anti-Scraping
Government websites in Japan (The National Diet) and South Korea (The National Assembly) don't offer unified, developer-friendly REST APIs. The data challenges included:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Inconsistent HTML Structures: Legacy server-rendered tables that change classes depending on the political party.&lt;/p&gt;

&lt;p&gt;Localized Traps: Dynamic localized scripts that render text based on specific local encodings.&lt;/p&gt;

&lt;p&gt;Temporal Decay: Politician profiles, faction realignments, and constituency boundary changes happen constantly, meaning static data decays within months.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Architecture: From Raw Scrapes to Structured JSON
To keep the search engine lightning-fast and up-to-date, I built a lightweight pipeline focused on predictability:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;[Target Gov Sites] -&amp;gt; [Python Scrapers (BeautifulSoup/Playwright)] -&amp;gt; [Data Normalization Pipeline] -&amp;gt; [Structured JSON/Markdown Engine] -&amp;gt; [Ghost/Frontend UI]&lt;br&gt;
Step 1: Normalizing the Schema&lt;br&gt;
Every politician, regardless of country, is normalized into a strict, unified data model. Here is a simplified abstraction of our data structure:&lt;/p&gt;

&lt;p&gt;JSON&lt;br&gt;
{&lt;br&gt;
  "id": "jp_diet_04912",&lt;br&gt;
  "name": {&lt;br&gt;
    "localized": "高市 早苗",&lt;br&gt;
    "english": "Sanae Takaichi"&lt;br&gt;
  },&lt;br&gt;
  "country": "Japan",&lt;br&gt;
  "house": "Representatives",&lt;br&gt;
  "constituency": "Nara 2nd District",&lt;br&gt;
  "gender": "Female",&lt;br&gt;
  "tenure_years": 30,&lt;br&gt;
  "last_updated": "2026-04"&lt;br&gt;
}&lt;br&gt;
Step 2: Overcoming the Multi-Dimension Filtering Bottleneck&lt;br&gt;
Instead of forcing users to rely on basic keyword search, the frontend engine handles multi-dimensional cross-filtering. Users can instantly filter by:&lt;/p&gt;

&lt;p&gt;Constituency/Region&lt;/p&gt;

&lt;p&gt;Gender representation&lt;/p&gt;

&lt;p&gt;Tenure windows (Years in office)&lt;/p&gt;

&lt;p&gt;This instantly turns a tedious research task into a 2-second query.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Key Takeaways for GovTech Projects
Building this taught me a few invaluable lessons about dealing with public records:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Expect Schema Drift: Government portals love to redesign without warning. Your scrapers must include robust error-handling and field validation to alert you when a selector breaks.&lt;/p&gt;

&lt;p&gt;Localization is More Than Translation: Mapping localized constituencies to standard geographical naming conventions requires a dedicated translation layer, not just generic LLM API calls.&lt;/p&gt;

&lt;p&gt;Check Out the Tool (And Share Your Feedback!)&lt;br&gt;
The database is fully updated to the latest 2026 political records and is completely free to use.&lt;/p&gt;

&lt;p&gt;👉 Explore the database here: &lt;a href="https://eastasiainsights.com/" rel="noopener noreferrer"&gt;East Asia Insights - Japan &amp;amp; SK Politician Lookup&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you are a data engineer, researcher, or anyone interested in open-source data and GovTech, I would love to hear your thoughts. How do you handle scraping and structures on legacy government websites? Let’s discuss in the comments below!&lt;/p&gt;

</description>
      <category>data</category>
      <category>dataengineering</category>
      <category>showdev</category>
      <category>sideprojects</category>
    </item>
    <item>
      <title>I built a lightweight Football Manager alternative because I don't have time for heavy games</title>
      <dc:creator>Chen Tao</dc:creator>
      <pubDate>Tue, 07 Jul 2026 03:35:36 +0000</pubDate>
      <link>https://dev.to/cksmct/i-built-a-lightweight-football-manager-alternative-because-i-dont-have-time-for-heavy-games-1jd4</link>
      <guid>https://dev.to/cksmct/i-built-a-lightweight-football-manager-alternative-because-i-dont-have-time-for-heavy-games-1jd4</guid>
      <description>&lt;p&gt;Hi guys, I am a web developer from China. &lt;/p&gt;

&lt;p&gt;Today I want to show you my side project: &lt;strong&gt;38-0 League&lt;/strong&gt;. It is a football (soccer) simulation game on web.&lt;/p&gt;

&lt;p&gt;I used to play a lot of Football Manager. But now I have a full-time job, I don't have time to play 5 hours just for half season. Sometimes I just have a stupid idea in my head: "What if Arsenal play in La Liga?" or "What if Haaland play in League 2?".&lt;/p&gt;

&lt;p&gt;So I write this game. You can custom teams, players, and just click one button. It will simulate 38 matches very fast.&lt;/p&gt;

&lt;p&gt;Here is some technical problems I met and how I fix them. Maybe it can help you if you build similar things.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Heavy Calculation in Browser
&lt;/h3&gt;

&lt;p&gt;Because I have no money to buy strong backend server, all match simulation runs in user's browser.&lt;br&gt;
Simulating 380 matches (20 teams play each other 2 times) is actually very fast in JS. The slow part is React rendering. At first, when I click "Simulate Season", the page freeze for 2 seconds. This is because I try to put all match logs and events into DOM state at same time.&lt;br&gt;
&lt;strong&gt;Solution:&lt;/strong&gt; I separate the logic and UI. The simulation function just return a big JSON object in memory. React only render the final League Table. The match details are lazy loaded. If you don't click the specific match, it never renders.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Cheap Leaderboard using Cloudflare D1
&lt;/h3&gt;

&lt;p&gt;I want a leaderboard so players can compete. But Redis or AWS RDS is too expensive for a free game. &lt;br&gt;
I try Cloudflare D1. It is serverless SQLite on edge. It is amazing!&lt;br&gt;
I write a Cloudflare Worker to receive high score from client, check a simple secret token, and save to D1 database. The latency is very low because the worker runs near the user. And the free tier gives me 100,000 writes per day. Very enough for me.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. SEO for SPA (Multi-language)
&lt;/h3&gt;

&lt;p&gt;This is my biggest headache. I use Vite + React. Google crawler don't like it. &lt;br&gt;
I want people from Spain or Brazil to find my game, so I need &lt;code&gt;/es&lt;/code&gt; and &lt;code&gt;/pt&lt;/code&gt; routes. I didn't use Next.js because I don't want to rewrite all my code. So I write a post-build Node script. &lt;br&gt;
After &lt;code&gt;npm run build&lt;/code&gt;, the script copy &lt;code&gt;index.html&lt;/code&gt; into many folders, and replace the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;meta&amp;gt;&lt;/code&gt; tags with correct language text statically. It's a bit dirty hack, but Google Search Console finally start to index my pages!&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Ads vs PageSpeed
&lt;/h3&gt;

&lt;p&gt;To pay the domain name, I put some Adsterra banners. But these ad scripts destroy my Core Web Vitals. My LCP and TBT become red in Lighthouse.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How I fix:&lt;/strong&gt; I delay the ad script. I use a simple event listener. Only when user scroll or move mouse, I append the ad &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt; to document body. So Lighthouse sees a clean page, and real users still see the ads. Win-win.
&lt;/h2&gt;

&lt;p&gt;If you like football, please try it here: &lt;strong&gt;&lt;a href="https://38-0.one" rel="noopener noreferrer"&gt;38-0.one&lt;/a&gt;&lt;/strong&gt;&lt;br&gt;
You can try the Sandbox mode to make your own crazy league. Let me know if you find bugs or have ideas! Thanks!&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fphk27a09f7u7tdzrjpj2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fphk27a09f7u7tdzrjpj2.png" alt=" " width="800" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Evolving my React Football Simulator: From getting crushed in 7-0 to the ultimate 8-0 World Cup challenge</title>
      <dc:creator>Chen Tao</dc:creator>
      <pubDate>Thu, 25 Jun 2026 08:55:46 +0000</pubDate>
      <link>https://dev.to/cksmct/evolving-my-react-football-simulator-from-getting-crushed-in-7-0-to-the-ultimate-8-0-world-cup-3mi</link>
      <guid>https://dev.to/cksmct/evolving-my-react-football-simulator-from-getting-crushed-in-7-0-to-the-ultimate-8-0-world-cup-3mi</guid>
      <description>&lt;p&gt;A few days ago, I wrote about building a hardcore football draft simulator in React where the RNG was so punishing that I couldn't even beat my own game using underdog teams.&lt;/p&gt;

&lt;p&gt;The feedback was awesome, but I quickly realized the game had limitations. The UI was a bit basic, the performance on mobile had quirks due to third-party scripts, and honestly, the concept needed to be bigger. With the 2026 World Cup on the horizon, I decided to tear down the codebase and rebuild it into something much more ambitious.&lt;/p&gt;

&lt;p&gt;Today, I’m launching the evolved version: 8-0.online. Here is exactly what changed under the hood and how I scaled the architecture from a simple draft tool to a multi-mode simulator with a 100/100 Lighthouse score.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Core Shift: From a Single Draft to 4 Game Modes&lt;/strong&gt;&lt;br&gt;
The original "7-0" version was a simple randomized draft. It was fun, but it got repetitive. For the new 8-0 version, I restructured the state management to support multiple isolated game flows. I introduced 4 entirely new modes, including:&lt;/p&gt;

&lt;p&gt;2026 Random XI: Pure chaos. You get what you get.&lt;br&gt;
The Redemption Tournament: A mode with slightly different risk/reward mechanics.&lt;br&gt;
Tech difference: Instead of a monolithic match simulation hook, I had to decouple the simulation logic from the drafting phase, allowing different rulesets to be injected dynamically depending on the selected mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. UI/UX Overhaul: Cinematic Dark Mode &amp;amp; Mobile App Shell&lt;/strong&gt;&lt;br&gt;
The old version had a standard, functional UI. For 8-0, I wanted a premium, cinematic feel. I completely ripped out the old CSS and implemented a comprehensive Dark Mode across the entire app.&lt;/p&gt;

&lt;p&gt;Tech difference: I built a mobile-first App Shell with a cinematic fade-out splash screen. I also had to hunt down and refactor dozens of hardcoded inline styles that were causing contrast issues. The result is a much denser, more immersive UI that actually feels like a high-end gaming dashboard rather than a simple web utility.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Hitting 100/100 Lighthouse (Taming Render-Blocking Ads)&lt;/strong&gt;&lt;br&gt;
One of the biggest headaches in the previous build was mobile web performance, specifically how third-party ad networks (like Adsterra) were destroying my Core Web Vitals.&lt;/p&gt;

&lt;p&gt;Tech difference: I implemented a strict lazy-loading strategy for the ad containers. By properly handling the visual whitespace (padding/margin) before the ads load, I eliminated layout shifts (CLS). Combined with ARIA accessibility patches and eliminating render-blocking dependencies, the React SPA now hits a perfect 100/100 on mobile PageSpeed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Taking Multi-lingual SEO to the Next Level (IndexNow)&lt;/strong&gt;&lt;br&gt;
In my last post, I mentioned using a Node.js post-build script to generate 13 different language static index.html files with hreflang tags. That worked great for structure, but waiting for search engines to crawl all those new 8-0 routes was taking too long.&lt;/p&gt;

&lt;p&gt;Tech difference: I didn't want to wait. Instead of manually submitting sitemaps via Bing/Yandex Webmaster UI, I wrote a custom Python automation script to integrate directly with the open IndexNow protocol. Now, whenever I push a new language update or a new game mode, the script pings the API with a custom verification key, forcing instant indexing across major search engines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Try to beat the RNG (Again)&lt;/strong&gt;&lt;br&gt;
The game is now fully migrated to its new home at 👉 &lt;a href="https://8-0.online/" rel="noopener noreferrer"&gt;8-0.online&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I still haven't managed to go undefeated in the 2026 Random XI mode with a tier-3 nation. If anyone manages to pull it off, please let me know what tactical setup you used!&lt;/p&gt;

&lt;p&gt;Also, I'd love to hear from fellow React devs: how do you typically handle state persistence for multi-step progressive web games without overcomplicating things with Redux?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>simulator</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>Building a hardcore football simulator in React (and failing to win my own game)</title>
      <dc:creator>Chen Tao</dc:creator>
      <pubDate>Fri, 19 Jun 2026 16:05:51 +0000</pubDate>
      <link>https://dev.to/cksmct/building-a-hardcore-football-simulator-in-react-and-failing-to-win-my-own-game-5glh</link>
      <guid>https://dev.to/cksmct/building-a-hardcore-football-simulator-in-react-and-failing-to-win-my-own-game-5glh</guid>
      <description>&lt;p&gt;I've always found commercial soccer draft games to be a bit too easy and forgiving. You just click some shiny cards and eventually win. So for my recent side project, I decided to build something much more punishing.&lt;/p&gt;

&lt;p&gt;The idea was simple: &lt;strong&gt;You must win 7 consecutive matches to lift the World Cup, using randomly drafted squads based on real FC 24 player stats.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Tech Stack
&lt;/h3&gt;

&lt;p&gt;I wanted to keep it lightweight, so I went with pure &lt;strong&gt;React + Vite&lt;/strong&gt;. No heavy state management libraries, just good old React hooks to handle the match simulation logic and RNG purely on the client side.&lt;/p&gt;

&lt;p&gt;One of the fun challenges was making it multi-lingual for global football fans. Instead of a backend, I wrote a Node.js post-build script that dynamically generates &lt;code&gt;index.html&lt;/code&gt; files with proper &lt;code&gt;hreflang&lt;/code&gt; tags for 13 different languages, ensuring it's SEO-friendly out of the box.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Result
&lt;/h3&gt;

&lt;p&gt;The match simulation turned out to be brutally realistic. I've been trying to win the tournament with the China PR squad, and I keep getting absolutely demolished by France or Brazil in the semi-finals. The RNG has no mercy. 😂&lt;/p&gt;

&lt;p&gt;If you want to try the simulation logic (or see if you can break the curse with an underdog nation like China), the game is live here: &lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://7-0worldcup.org" rel="noopener noreferrer"&gt;7-0 World Cup Draft Simulator&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Would love to hear any feedback from fellow React devs on how to better balance client-side RNG for games like this!&lt;/p&gt;

</description>
      <category>react</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
