Phaser + Vite Setup: A Real Project Structure (2026)

Phaser + Vite Setup: A Real Project Structure (2026)

Honesty note: this is my actual Phaser project setup (Merge Fish 2048) — the vite.config.js, package.json, and scene structure I ship with. My project is JavaScript; I’ll show how to add TypeScript to the same setup, marked clearly as the optional path.

TL;DR

  1. Vite + Phaser is the lowest-friction HTML5 game stack in 2026: instant dev server, npm run build → a dist/ folder you can deploy anywhere (Cloudflare Pages, any static host, any portal).
  2. Three Vite settings matter for games: base: './' (relative paths so the build works in any iframe/subfolder), assetsInlineLimit: 0 (keep small assets as files, not inlined base64), and manualChunks to split the Phaser library so your game code updates don’t redownload the engine (see the size guide).
  3. Structure by responsibility: scenes/ (UI flows), game/ (pure logic — board, levels, catalog), utils/ (storage, audio, ads, buttons). Logic separate from rendering = testable and AI-friendly (AI workflow).
  4. TypeScript is optional and cheap to add: install typescript, add a tsconfig.json (strict, moduleResolution: bundler), rename .js.ts. Phaser ships full TS types. The rest of the stack is unchanged.

The real config

// vite.config.js — my exact file
import { defineConfig } from 'vite';

export default defineConfig({
  base: './',                                  // relative asset paths — works in iframes/subfolders
  build: {
    outDir: 'dist',
    assetsInlineLimit: 0,                      // keep assets as separate files (no base64 bloat)
    rollupOptions: {
      output: {
        manualChunks: {
          phaser: ['phaser']                   // Phaser in its own chunk — cacheable across builds
        }
      }
    }
  },
  server: {
    port: 5173,
    open: true                                 // auto-open browser on dev start
  }
});
// package.json — the essentials
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview --port 8080"
  },
  "dependencies": { "phaser": "^3.90.0" },
  "devDependencies": { "vite": "^5.4.0" }
}

Three choices worth copying:

The scene structure that scales

src/
├── main.js                 # Phaser.Game config + scene registration
├── config.js               # game-wide constants (dimensions, physics, storage keys)
├── scenes/                 # UI flows — Boot, Menu, Help, LevelSelect, Game, Aquarium, Rank, Settings, GameOver
├── game/                   # PURE logic, no Phaser imports — Board, FishCatalog, LevelConfig
└── utils/                  # Phaser-touching helpers — Storage, AudioManager, RewardManager, ButtonFactory

The rule that keeps it maintainable: game/ never imports Phaser. Board logic (merge rules, win/lose checks) is plain JavaScript — testable in a headless run, portable to another engine if you ever switch, and fully visible to AI tools. scenes/ and utils/ are the Phaser-facing layer. (My 9-scene structure is covered in more depth in the scene management guide.)

Adding TypeScript (the optional path)

My project is JavaScript, but the same setup takes TS in three steps:

npm i -D typescript
// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "strict": true,
    "skipLibCheck": true,
    "types": ["vite/client"]
  },
  "include": ["src"]
}

Then rename .js.ts — Phaser ships complete type definitions, so you get autocomplete on this.physics, this.add, scene lifecycle, etc. The Vite config and build pipeline are unchanged; Vite handles TS transpilation natively.

Why I stayed on JS: my project is small, the types would help most in game/ (which is pure logic I test headlessly anyway), and the AI-assisted workflow reads plain JS just as well. If your game grows past a few scenes or you share code with teammates, TS is the right call.

What this produces

npm run build → a dist/ folder with:

That folder deploys to Cloudflare Pages, GitHub Pages, any static host, or straight into a portal iframe. The asset pipeline feeds it game-ready assets; the marketing guide explains where to put the result.

Pitfalls

  1. Forgetting base: './' — the #1 “works locally, breaks deployed” bug for games in iframes/subfolders.
  2. Inline everything — small PNGs become base64 in the JS bundle; you lose HTTP caching and bloat the entry file.
  3. Mixing logic with scenes — board rules inside a scene = untestable, AI-hostile, and painful to port.
  4. TS without moduleResolution: bundler — Vite-based projects need the bundler resolution; the classic “cannot find module ‘phaser’” trap.
  5. No .gitignore for node_modules/dist — you’ll push megabytes of junk to the repo (the deploy pipeline builds it fresh anyway).

Bottom line

Phaser + Vite is the pragmatic 2026 web-game stack: three Vite settings (base, assetsInlineLimit, manualChunks) make the build portable and lean; a responsibility-split structure (pure logic vs Phaser layer) keeps it maintainable and AI-friendly; TypeScript is a three-step optional addition with full Phaser types. This exact setup ships my game today — npm run builddist/ → deployed, no engine binary, ~1-2 MB gzipped (why that matters).