emulationjavascriptretro-gamingweb-development

Building the Retro Game Arcade: 400 Games in Your Browser

How I built a browser-based retro game emulator with 400 games from NES, Game Boy Color, and Game Boy Advance — all running client-side with zero server processing.

Cooper Howling

I own a lot of old video games. Cartridges, discs, boxes stacked in closets. They’re legally mine, but I never play them because digging out a console, hooking it up to a TV, and finding the right cables is a hassle.

So I built a retro game arcade that runs entirely in the browser. 400 games. Three systems. No downloads, no installs, just click and play.

The Problem with Physical Games

Retro gaming is great in theory. In practice, it’s a pain:

  • Hardware degrades — Cartridge contacts corrode, disc drives fail, capacitors leak
  • Setup is tedious — Finding cables, switching inputs, dealing with old controllers
  • Space is limited — I don’t have room for multiple consoles hooked up permanently
  • Portability is zero — Can’t take my collection on a trip

Emulation solves all of this. And modern browsers are powerful enough to run emulators entirely client-side.

The Tech Stack

EmulatorJS

I used EmulatorJS, an open-source JavaScript emulator that runs via WebAssembly. It supports NES, Game Boy Color, Game Boy Advance, and more.

The key advantage: everything runs in the user’s browser. The server just serves ROM files as static assets. No server-side processing, no compute costs, infinite scalability.

ROM Storage

All 400 ROMs are stored on my self-hosted server at /var/www/howling-global/public/roms/{system}/. They’re organized by system:

  • /roms/nes/ — NES games
  • /roms/gbc/ — Game Boy Color games
  • /roms/gba/ — Game Boy Advance games

These are legally owned physical copies that I’ve dumped myself. No piracy, no grey-area downloads.

Auto-Discovery API

I built an API endpoint at /api/games.json that scans the ROM folders and returns a JSON list of all available games. This means I can add new games by just dropping ROM files into the folder — no database updates, no manual configuration.

The API runs server-side (Astro endpoint), scans the directories, and returns metadata like filename, system, and path.

Client-Side Game Browser

The /ranks/play page is a dynamic game browser built with Astro and vanilla JavaScript. It fetches the game list from the API, renders a searchable grid, and lets users filter by system.

Click a game, and EmulatorJS loads it in an iframe. Save states are stored in browser local storage, so progress persists across sessions.

Architecture Decisions

Why Client-Side Emulation?

Running emulation in the browser has massive advantages:

  1. Zero server load — The server just serves static files. Emulation happens on the user’s device.
  2. Infinite scalability — 1 user or 1000 users, server load is the same.
  3. No latency — No streaming lag, no input delay. It’s as responsive as native emulation.
  4. Privacy — Save states never leave the user’s browser. No cloud storage, no tracking.

The downside: older devices might struggle with more demanding games. But modern phones and laptops handle it fine.

Why Astro?

Astro is perfect for this use case:

  • Static site generation — The game browser page is pre-rendered at build time
  • API routes — Server-side endpoints for the ROM scanner
  • Zero JavaScript by default — Only loads EmulatorJS when needed
  • Fast builds — Rebuilds in seconds, even with 400 games

I could have used Next.js or SvelteKit, but Astro’s simplicity won out.

Game Selection

I didn’t just dump every ROM I own. I curated the collection:

  • Quality over quantity — Only games I’d actually recommend
  • Variety — Mix of genres, difficulty levels, and playstyles
  • Nostalgia factor — Games I grew up with, plus classics I missed

The result is 400 games that are actually worth playing, not a bloated library of shovelware.

Lessons Learned

Browser Emulation Is Mature

Five years ago, browser-based emulation was janky. Today, it’s rock solid. EmulatorJS handles NES, GBC, and GBA flawlessly.

WebAssembly made this possible. Emulators compiled to WASM run at near-native speed.

File Serving Is the Bottleneck

The biggest performance issue isn’t emulation — it’s loading ROMs. Larger ROM files can take time to download over the network.

I mitigated this with:

  • Cloudflare caching — ROMs are cached at the edge, so repeat loads are instant
  • Compression — ROMs are served gzipped, cutting transfer size by ~30%
  • Lazy loading — Only load the ROM when the user clicks “Play”

Save States Are a Game-Changer

Browser local storage lets users save their progress without any server-side infrastructure. It’s seamless, private, and works offline.

The only downside: save states are device-specific. If you switch browsers or devices, you lose your progress. I could add cloud sync, but that adds complexity and privacy concerns.

For now, local-only saves are good enough.

I was worried about hosting ROMs publicly, even though I own the physical copies. Turns out, as long as you’re not distributing copyrighted material you don’t own, you’re fine.

My ROMs are:

  • Dumped from cartridges/discs I physically own
  • Not shared publicly (the site is for personal use)
  • Stored on my own server (no third-party hosting)

This is legally equivalent to ripping a CD you own and playing it on your phone. Fair use.

What’s Next

More Systems

I’d love to add SNES, Sega Genesis, and PlayStation 1. EmulatorJS supports them, I just need to dump more ROMs.

Better UI

The current game browser is functional but basic. I want to add:

  • Cover art — Thumbnails for each game
  • Ratings — Let users vote on their favorites
  • Playlists — Curated lists like “Best Platformers” or “Hidden Gems”

Multiplayer

Some emulators support netplay (online multiplayer). Adding this would let me play classic games with friends remotely. It’s technically possible, but requires WebRTC and signaling servers.

Maybe in v2.

The Nostalgia Factor

Building this arcade brought back memories of renting games from Blockbuster, grinding Pokémon Red on long car trips, and discovering hidden gems in bargain bins.

These games aren’t graphically impressive by modern standards. But they’re fun. They’re designed around tight mechanics and clever level design, not photorealistic graphics or cinematic cutscenes.

Playing them again reminded me why I fell in love with games in the first place.

Try It Yourself

If you want to play, head over to Retro Game Arcade and browse the collection. No account required, no downloads, just pick a game and start playing.

And if you’re thinking about building your own emulator site: it’s easier than you think. EmulatorJS does the heavy lifting, you just need to serve the ROMs and build a UI.


Tech Stack:

  • EmulatorJS (client-side emulation via WebAssembly)
  • Astro (static site + API routes)
  • Self-hosted on Ubuntu via Cloudflare Tunnel
  • ROMs stored as static files, served via nginx

Play the games: howlingglobal.com/ranks/play