DEV Community

RAYAN B
RAYAN B

Posted on

How I Built a Browser-Based GBA/GBC/GB Emulator with mGBA WASM and Astro

The Problem

Every retro gamer knows the struggle: you want to play Pokemon Emerald or Zelda: The Minish Cap, but you're stuck at a computer without your GBA, or you're on a Chromebook at school, or you just don't want to install sketchy emulator software.

I built EggerMath to solve this — a free, browser-based emulator that runs GBA, GBC, and GB games instantly. No downloads, no ads, no signup. Just click and play.

Here's how I built it using mGBA WebAssembly, Astro, and Cloudflare Pages.

Architecture Overview

┌─────────────────────────────────────────┐
│  Astro Static Site (SSG)                │
│  52 game pages × 11 languages = 651     │
├─────────────────────────────────────────┤
│  mGBA WASM (WebAssembly)                │
│  Compiled from C → WASM with pthreads   │
├─────────────────────────────────────────┤
│  MEGA SDK (client-side ROM loading)     │
│  ROMs served from MEGA CDN              │
├─────────────────────────────────────────┤
│  Cloudflare Pages (edge deployment)     │
│  COOP/COEP headers for SharedArrayBuffer│
└─────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Step 1: mGBA WebAssembly Compilation

The core challenge was getting mGBA — a mature GBA emulator written in C — to compile to WebAssembly. The mGBA project provides a WASM build that works with pthreads for multi-threaded emulation.

Step 2: COOP/COEP Headers (The Tricky Part)

WebAssembly with pthreads requires SharedArrayBuffer, which browsers only enable when the page has Cross-Origin-Opener-Policy: same-origin and Cross-Origin-Embedder-Policy: require-corp.

But Cloudflare serves JS and WASM files with its own headers. I had to strip COOP/COEP from static asset responses while keeping them on HTML pages:

# public/_headers
/*
  Cross-Origin-Opener-Policy: same-origin
  Cross-Origin-Embedder-Policy: require-corp

/*.js
  Cross-Origin-Opener-Policy: 
  Cross-Origin-Embedder-Policy: 

/*.wasm
  Cross-Origin-Opener-Policy: 
  Cross-Origin-Embedder-Policy: 
Enter fullscreen mode Exit fullscreen mode

This lets the main page enable SharedArrayBuffer while WASM/JS files load without conflicting headers.

Step 3: ROM Loading via MEGA SDK

Instead of hosting ROMs myself (legal gray area + bandwidth costs), I integrated the MEGA JavaScript SDK to load ROMs directly from a MEGA folder. ROMs load client-side, cached in IndexedDB for repeat plays. The MEGA folder acts as a free CDN with generous bandwidth.

Step 4: Astro Static Generation

I chose Astro for its static-site generation. Each game gets its own page with SEO-optimized titles, 11 language translations with proper hreflang tags, VideoGame schema for rich snippets, and GEO-citable answer blocks for AI search engines.

This generates 651 pages — each one an indexable landing page for a specific game.

Step 5: Save States with IndexedDB

Players need save states. I implemented a 10-slot save system using IndexedDB. States persist across sessions. The Continue Playing bar shows recently played games with cover art and progress.

Step 6: Mobile-First Touch Controls

The emulator needed to work on phones. I built D-pad and A/B buttons with 3D CSS styling, touch event handlers for mobile interaction, fullscreen mode with landscape lock via Screen Orientation API, and an iOS compatibility shim for older Safari versions.

Results

  • 52 games across GBA, GBC, and GB
  • 11 languages (EN, PT-BR, ES, JA, DE, FR, RU, KO, IT, ID, AR)
  • 651 pages built in ~10 seconds
  • Zero ads, zero signup, zero download
  • Save states with 10 slots per game
  • Full-screen mobile with touch controls
  • Deployed on Cloudflare Pages with unlimited bandwidth

What I Learned

  1. WebAssembly + SharedArrayBuffer is fragile — Browser security models conflict with WASM threading. The COOP/COEP header dance is non-obvious.
  2. Static generation scales — 651 pages generate in 10 seconds. No server needed.
  3. MEGA as a CDN works — Client-side MEGA SDK loads ROMs without server-side hosting. Legally safer, practically free.
  4. i18n is underrated for SEO — 11 languages × 52 games = 572 unique indexable pages. Most emulators only target English.
  5. AI search needs citable blocks — I added front-loaded answer paragraphs specifically designed to be cited by ChatGPT, Perplexity, and Google AI Overviews.

Try It

eggermath.com — pick a game, click play. Works on desktop and mobile.

The source code for the WASM integration is at github.com/rayanbaig796-crypto/gba-wasm-web.


Built with Astro, mGBA WASM, MEGA SDK, and Cloudflare Pages.

Top comments (0)