DEV Community

Cover image for Why Modern Gaming Wikis Suck (And How I Built a 100/100 Lighthouse Wiki with Next.js & TypeScript)
Chen Tao
Chen Tao

Posted on

Why Modern Gaming Wikis Suck (And How I Built a 100/100 Lighthouse Wiki with Next.js & TypeScript)

If you have tried looking up a boss strategy or character build on a modern gaming wiki recently, you know the pain.

You open a page on your phone, and before the first sentence renders:

  1. Two sticky video overlays dock themselves to the corners of your screen.
  2. A full-screen interstitial ad hijacks your tap.
  3. The page layout violently jumps 400 pixels down (Cumulative Layout Shift at its finest) as five different ad networks race to auction your attention.
  4. Total transferred payload? 18 MB. Total DOM nodes? 6,500+. Time to Interactive? 11 seconds.

When Bit Reactor and EA announced STAR WARS Zero Company—a tactical turn-based RPG built by veteran XCOM developers—I decided to take a stand. I didn't want another bloated wiki. I wanted a lightning-fast, zero-bloat tactical database that loads in under 200ms, respects battery life, and provides verified gameplay theorycrafts.

Here is the engineering breakdown of how I built Zero Company Guide with Next.js 16, TypeScript, and a strict pre-build validation pipeline.


1. The Core Architecture: Data as Code

Most gaming databases rely on a heavy Headless CMS, WordPress, or an unstructured MediaWiki backend. For a focused tactical guide, all of those introduce runtime database latency, cache-invalidation bugs, and hosting costs.

Instead, I treated all game entities—operators, skills, story missions, bonding matrices, and build synergies—as strict TypeScript structures:

// src/data/operators.ts
export interface Operator {
  id: string;
  name: string;
  callsign?: string;
  role: "Vanguard" | "Commando" | "Infiltrator" | "Guardian" | "Tech Specialist";
  faction: "Republic" | "Underground" | "Mercenary";
  baseStats: {
    hp: number;
    mobility: number;
    willpower: number;
    baseAdvantage: number;
  };
  signatureAbility: {
    name: string;
    description: "string;"
    cooldownTurns: number;
  };
}

export const operators: readonly Operator[] = [
  // Fully typed, validated at compile-time
] as const;
Enter fullscreen mode Exit fullscreen mode

Why Data-as-Code Wins:

  • Zero Runtime Query Overhead: All data is statically compiled into static HTML/JSON during next build.
  • Refactoring Safety: Changing a property name in an interface flags every affected component and view at compile time across the entire codebase.
  • Git as Single Source of Truth: Community PRs for patch balance updates can be code-reviewed like regular software changes.

2. The Pre-Build Defensive Pipeline

One of the biggest problems with gaming guides is stale data and dead links. As a game goes through balance patches, guides quietly rot.

Rather than relying on human memory, I wired up an automated pre-build test gate in package.json:

{
  "scripts": {
    "prebuild": "node scripts/gen-content-dates.mjs && node scripts/audit-data.mjs && node scripts/audit-trend-coverage.mjs && node scripts/audit-links.mjs && node scripts/audit-canonical.mjs",
    "build": "next build"
  }
}
Enter fullscreen mode Exit fullscreen mode

Here is what these standalone audit scripts enforce before any deployment can succeed:

A. Freshness & Anachronism Guard (audit-data.mjs)

Guarantees that no developer accidentally sets a future timestamp on a guide and flags any data collection older than 14 days for manual review:

// scripts/audit-data.mjs
const todayStr = new Date().toISOString().slice(0, 10);

for (const m of txt.matchAll(/"(\/[^"]*)":\s*"(\d{4}-\d{2}-\d{2})"/g)) {
  const [, route, date] = m;
  if (date > todayStr) {
    console.error(`[audit-data] ❌ Future date detected: ${route} = ${date}`);
    process.exitCode = 1;
  }
}
Enter fullscreen mode Exit fullscreen mode

B. Trailing-Slash Canonical Auditor (audit-canonical.mjs)

A subtle SEO bug in static hosting (like Cloudflare Pages or AWS S3) is the redirect loop between /page and /page/.

Our auditor recursively parses all static output HTML files in out/, ensuring:

  • Every <link rel="canonical"> has a deterministic trailing slash.
  • OpenGraph og:url matches the canonical URL 1:1.
  • sitemap.xml has 0 missing routes and 0 duplicate canonicals.
// Verifies out/**/*.html for canonical integrity
const canonicalMatch = html.match(/<link rel="canonical" href="([^"]+)"/);
if (!canonicalMatch || !canonicalMatch[1].endsWith("/")) {
  throw new Error(`Invalid canonical in ${file}: must end with a trailing slash!`);
}
Enter fullscreen mode Exit fullscreen mode

3. Media Facade: Eliminating Third-Party Embed Bottlenecks

Embedding YouTube gameplay analysis or trailer clips normally kills your PageSpeed score. A single standard <iframe> loads ~1.2 MB of scripts, fonts, and tracking beacons from Google before the user even clicks play.

We solved this with a Video Facade Component:

// src/components/YouTubeEmbed.tsx
"use client";

import { useState } from "react";
import Image from "next/image";

export default function YouTubeEmbed({ videoId, title, posterUrl }: Props) {
  const [isPlaying, setIsPlaying] = useState(false);

  if (!isPlaying) {
    return (
      <div 
        className="group relative aspect-video w-full cursor-pointer overflow-hidden rounded-lg bg-surface"
        onClick={() => setIsPlaying(true)}
      >
        <Image
          src={posterUrl}
          alt={title}
          fill
          sizes="(max-width: 768px) 100vw, 800px"
          className="object-cover transition-transform duration-300 group-hover:scale-105"
          loading="lazy"
        />
        <button 
          aria-label={`Play ${title}`}
          className="absolute inset-0 m-auto flex h-14 w-14 items-center justify-center rounded-full bg-primary/90 text-white shadow-xl transition-all group-hover:scale-110"
        ></button>
      </div>
    );
  }

  return (
    <iframe
      className="aspect-video w-full rounded-lg"
      src={`https://www.youtube-nocookie.com/embed/${videoId}?autoplay=1`}
      title={title}
      allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
      allowFullScreen
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

Result:

  • Initial page load payload dropped from 2.1 MB to 84 KB.
  • Total Blocking Time (TBT) dropped from 380ms to 0ms.

4. Dark Tactical UI with Zero CLS

For visual styling, I chose Tailwind CSS with custom HSL tokens to evoke a high-contrast Republic tactical terminal.

Typography matters: we paired Orbitron for tactical headings and Inter for dense, readable tabular data, loaded via next/font/google with display: 'swap' and automated CSS variable fallbacks to eliminate Flash of Unstyled Text (FOUT).

/* Custom tactical design tokens */
:root {
  --bg-primary: #0a0d14;
  --surface: #121824;
  --borderline: #1e293b;
  --accent-cyan: #38bdf8;
  --accent-gold: #f59e0b;
}
Enter fullscreen mode Exit fullscreen mode

Every card, table, and data grid has explicit CSS container sizes or aspect-ratio locks. When the page renders on mobile, there is 0 Cumulative Layout Shift (CLS score: 0.00).


5. The Performance Scorecard

Here is the Lighthouse audit for the production site:

Metric Industry Standard Wiki Zero Company Guide
Page Weight (Initial) 14.8 MB 68 KB
HTTP Requests 120+ 9
Performance Score 24 / 100 100 / 100
Largest Contentful Paint (LCP) 4.8s 0.5s
Cumulative Layout Shift (CLS) 0.38 0.00
Total Blocking Time (TBT) 1,420ms 0ms

Key Takeaways

  1. Stop defaulting to dynamic servers for static content: 95% of gaming databases do not need server-side rendering or database queries on every HTTP hit. SSG is cheaper, faster, and un-crashable.
  2. Build your audit pipeline into prebuild: Don't rely on manual QA to catch broken links, trailing slash redirect bugs, or missing metadata. Automate it with simple Node.js scripts.
  3. Facade patterns are mandatory for embeds: Never load raw third-party iframes on page load. A static WebP placeholder with a click-to-load handler saves megabytes of unnecessary client bandwidth.

If you are a tactics fan or just want to see the UI in action, check out the live site at starwarszerocompany.blog.

Happy to answer any questions about the pre-build pipeline or SSG architecture in the comments below!

Top comments (0)