Browser front-end for OnaNES, a JavaScript NES emulator. Pick a game, and it runs entirely in your browser — no backend, no plugins.
It's a static single-page app hosted on GitHub Pages. The emulator core ships
as the nes-emulator npm package;
this repo is just the UI around it.
- Play NES ROMs in the browser at 60 fps
- Three swappable renderers: PixiJS (2D/WebGL), ThreeJS (2D), and ThreeJS 3D (the game projected onto a 3D model)
- Quick save / load states (stored in
localStorage) - Per-game cheats
- First-run "How to Play" overlay plus an always-visible controls reference
- Fullscreen mode
The game is played with the keyboard:
| NES button | Key |
|---|---|
| D-pad | Arrow keys |
| A | C |
| B | X |
| Start | Enter |
| Select | Shift |
Mobile/touch controls are not implemented yet — desktop keyboard only for now.
app/ Create React App front-end (the deployed site)
public/ index.html, 404.html (SPA fallback), favicon, .nojekyll
src/
components/ React components (NES screen, renderers, modals, grids)
config/ settings + routes
utils/ ROM manifest, fetch helper, three.js OrbitControls
.github/workflows/ GitHub Pages build & deploy
api/ legacy Express stub — unused by the Pages deployment
ROMs and cover art are not stored here. They live in a separate (private)
data repo and are copied into the build at deploy time, then served from
/<base>/media/data/<slug>.{nes,jpg} on the same origin as the app.
Requires Node 16 (the toolchain is react-scripts 2.x / webpack 4, which
predates OpenSSL 3 and newer Node).
cd app
npm install --legacy-peer-deps
# ROMs aren't in this repo. Drop the .nes/.jpg files (named <slug>.nes /
# <slug>.jpg, matching app/src/utils/roms.js) into app/public/media/data/
# so they're served locally:
mkdir -p public/media/data
# cp /path/to/your-roms/*.{nes,jpg} public/media/data/
npm start # http://localhost:3000The
npm run build/npm startscripts contain a stray--openssl-legacy-providerflag that breaks them on Node 16; runnpx react-scripts start/npx react-scripts builddirectly if needed.
Pushing to master triggers .github/workflows/deploy.yml, which:
- checks out this repo and a separate private repo holding the ROM data,
- builds the app on Node 16 (
react-scripts build), - copies the ROMs/covers into
build/media/data/, - publishes the result to GitHub Pages.
Two settings make this work on a fork:
- Base path — the site lives at
https://<user>.github.io/<repo>/, so thehomepagefield inapp/package.jsonmust match your repo name (it drivesPUBLIC_URL, the routerbasename, and the ROM paths). Deep links survive Pages' lack of server-side routing via thepublic/404.htmlredirect trick. ROM_DATA_TOKEN— because the ROM-data repo is private, the workflow needs a repo secret containing a fine-grained PAT with read-only Contents access to it. (Drop this step if your data repo is public.)
No ROMs are included. You supply your own and list them in
app/src/utils/roms.js ({ value, label, slug }), with files named
<slug>.nes (and optional <slug>.jpg cover) in the data folder.
nes— the emulator engine (CPU/PPU)
GPL-3.0 — see LICENSE.