Browser Game Developer Setup 2026 — The Toolchain Behind 203 Components
Go deeper on this topic
Browser Game Development
Game loop design, collision detection, pathfinding, and tooling for browser games
Article 1 of 7 in this series.
Next reads
Building a Breakout Game in the Browser — Mastering Reflection Angles and Level Design with Canvas API
Implementing a Breakout game using Canvas API and requestAnimationFrame. Covers paddle reflection angle calculation, block layout level design, and score management through component implementation.
Comparing Maze Generation Algorithms — Structural Differences Between Recursive Backtracking, Kruskal, and Prim
Comparing the characteristics of multiple maze generation algorithms including recursive backtracking, Kruskal's, and Prim's, and the structural differences in the mazes they produce.
Visualizing Pathfinding Algorithms — Watching A*, BFS, and DFS Solve a Maze
Techniques for visualizing the search process of A*, BFS, and DFS implemented in the MazeRunner component, with a comparison of each algorithm.
Use the topic hub as a map, then continue to the next article in the series. New here? Start at the home hub →
Introduction
This portfolio site has around 200 interactive components. Fluid simulation, pinball, maze generation, piano, cellular automata — the result of continuously adding small demos that run in the browser.
Developing and maintaining this many components efficiently requires careful toolchain selection. But my motto is "fewer tools, deeper mastery of each." Every new tool you introduce increases cognitive load. Configuration files multiply. Things your teammate (in this case, future me) needs to remember grow.
This article introduces the toolchain that actually supports ~200 components. It's not a flashy setup. bun, Biome, Vite, Claude Code. These four are the core, and each is used to its fullest.
Editor: Terminal First
Claude Code as the Primary Development Tool
When asked what editor I use, the honest answer is "the terminal." Specifically, Claude Code (CLI) is my primary development tool.
# Create a new component
claude "Create a PendulumWave component. Place it in src/components/interactions/PendulumWave.tsx"
# Fix a bug
claude "Fix the bug in FluidSimulation where rendering breaks when Canvas size is 0"
# Refactor
claude "Unify the mouse events in this file to Pointer Events"The bulk of code writing is delegated to Claude Code. What I do is give instructions on "what to build" and review the output.
VS Code is a "Viewing" Tool
I also use VS Code, but its role has changed. Not for writing code, but for file browsing and diff review.
- Surveying project structure via the file tree
- Reviewing Git diffs side by side
- Comparing multiple files in tabsIn the age of AI writing code, the editor has become a tool for "viewing." Investing time in polishing how you give instructions to Claude Code yields better returns than customizing keybindings or curating extensions.
Package Manager: bun
Why I Migrated from npm to bun
I switched from npm to bun for a simple reason: it's fast.
# Installing dependencies
bun install # 5-10x faster than npm install
# Running scripts
bun run dev # Start dev server
bun run build # Production build + tsc
bun run check # Biome lint + formatWith ~200 components, dependencies add up. The speed of bun install is noticeable at a visceral level. Since it runs on every CI build, the savings compound.
bunx for MCP Servers Too
I use bunx instead of npx. MCP (Model Context Protocol) server startup is also unified with this.
bunx @anthropic/mcp-server-filesystem /path/to/projectUnifying the package manager eliminates the decision of "do I run this with npx or bunx?" It's a small thing, but there's real value in eliminating one decision.
bun in CI/CD Too
GitHub Actions also uses bun.
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: bun-\${{ hashFiles('bun.lock') }}oven-sh/setup-bun@v2 for setup and actions/cache@v4 for caching. Using the same package manager locally and in CI reduces "works locally but fails in CI" problems.
No reason to go back to npm.
Linter/Formatter: Biome
ESLint + Prettier → Unified with Biome
I used to use the ESLint + Prettier combination. Managing configuration files for both was quietly annoying. Conflicting rules, plugin version management, dual maintenance of .eslintrc and .prettierrc.
After unifying on Biome, there's just one configuration file: biome.json.
{
"formatter": {
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"semicolons": "asNeeded"
}
}
}Single quotes, 2 spaces, 100 width, semicolons asNeeded. All contained in one file.
Development Flow
# Local: lint + format in one command
bun run check
# CI: read-only check (doesn't modify files)
bunx biome ci .bun run check runs lint and format simultaneously. biome ci is the CI command that returns an error if there are issues but doesn't modify files.
The comfort of having a single configuration file exceeded my expectations. When starting a new project, just copy one biome.json and you're done. Freedom from ESLint's plugin dependency hell.
Build: Vite
Fast HMR
Vite's HMR (Hot Module Replacement) is fast. About 50ms from saving a file to seeing the change in the browser. This speed holds even in a project with ~200 components.
When developing Canvas-based components, you repeat the cycle of tweaking parameters and checking results dozens of times. Fluid simulation viscosity coefficients, particle decay rates, physics engine gravity constants. Whether this cycle takes 50ms or 2 seconds makes a fundamental difference in experience.
Plugin Order is Critical
The most important thing in Vite configuration is plugin order.
// vite.config.ts
export default defineConfig({
plugins: [
tailwindcss(), // 1. Tailwind CSS
tsConfigPaths(), // 2. Path alias resolution
cloudflare(), // 3. Cloudflare Pages
tanstackStart(), // 4. TanStack Start
react(), // 5. React
],
})Get this order wrong and the build won't pass, or you'll get mysterious errors after deployment. In particular, Cloudflare Pages must be placed before TanStack Start. It took half a day to figure this out.
Vite's speed fundamentally changes the development experience. Just as you can't go back to the webpack era, you can't go back to pre-Vite.
Version Control: Git Worktree
Isolate with Worktrees, Not Branches
With ~200 components in development, you often want to work on multiple tasks in parallel. Fix component A's bug while developing component B's new feature and running a refactor on component C.
Doing this with Git branches alone leads to a storm of git stash and git checkout. Stash work in progress, switch to another branch, finish, switch back, stash pop — this is an accident waiting to happen.
Git Worktree physically separates the file system.
# Start independent tasks with cc <name> in separate worktrees
cc fix-fluid-sim # Create worktree and start Claude Code there
cc add-pinball # Another worktree with another Claude Code session
cc refactor-physics # Yet another worktreeEach worktree is an independent directory. File conflicts physically can't happen.
Parallel Development in Practice
Open 3-4 terminals, each running a Claude Code session. One terminal, one task.
Terminal 1: cc fix-audio-context → Fix AudioContext bugs
Terminal 2: cc add-maze-solver → Add new maze solver
Terminal 3: cc refactor-canvas → Refactor Canvas components
Terminal 4: Main worktree → Review diffs, mergeWhen each task is done, it auto-merges to the staging branch. Conflicts are rare because the ~200 components are each in independent files. FluidSimulation.tsx and MazeGenerator.tsx can be modified simultaneously without conflicting.
The reason for isolating with worktrees rather than branches is clear: the file system is physically separated, so the cognitive load of "which branch am I on again?" drops to zero.
CI/CD: GitHub Actions + Blacksmith
Blacksmith Runners
CI uses GitHub Actions with Blacksmith runners. Faster than GitHub's default runners at lower cost.
runs-on: blacksmith-2vcpu-ubuntu-2404Two-Step Deploy
The deployment pipeline is simple.
steps:
- run: bun install
- run: bun run build
- run: bunx wrangler pages deploybun run build performs the production build (including TypeScript type checking), and wrangler deploys to Cloudflare Pages.
Skipping Unnecessary Builds
dorny/paths-filter is used to skip jobs based on changed file paths.
- uses: dorny/paths-filter@v3
id: changes
with:
filters: |
src:
- 'src/**'
- 'package.json'
- 'bun.lock'Running builds and deploys for documentation-only changes is wasteful. Path filters prevent this.
AI Development: Claude Code
Providing Project Context with CLAUDE.md
The most important feature of Claude Code is CLAUDE.md. A markdown file placed at the project root, automatically loaded at session start.
# CLAUDE.md
## Tech Stack
- Framework: TanStack Start v1.x (React 19)
- Styling: Tailwind CSS v4
- Animation: Motion (motion/react) + Canvas API
- Deploy: Cloudflare Pages
## Important Notes
- motion imports use motion/react (not framer-motion)
- Path alias ~/ → ./src/
- Vite plugin order: tailwindcss → tsConfigPaths → cloudflare → tanstackStart → reactJust writing this ensures Claude Code starts every session already knowing "this project uses motion/react not framer-motion" and "the path alias is ~/." Consistent code is generated across all ~200 components.
takt Workflow
I built a system that automatically selects workflows based on task size.
| Size | Workflow | Example |
|---|---|---|
| Typo fix, 1-line change | Implement directly | Variable name correction |
| Bug fix, 3 files or fewer | quick-fix | AudioContext error handling |
| New feature, multiple files | plan-implement-review | New game component |
| Major feature, architecture change | spec-then-build | Physics engine design change |
Applying a full review process to small tasks is wasteful, and proceeding without review on large tasks is risky. The right process for the right scale, selected automatically.
One Session, One Task
This is the most important principle when using Claude Code.
Same problem fails twice → /clear
Between unrelated tasks → /clearLLMs have a context window constraint. Cramming multiple tasks into one session causes old information to be compressed (effectively forgotten). Around the 30th file, fix accuracy starts to decline.
So split tasks. Do one thing per session. When it's done, reset with /clear. Following this principle alone dramatically stabilizes output quality.
AI is just one of the tools. But it's undeniably the tool that has changed productivity the most.
Summary: Browser Game Development Setup
Writing out my toolchain, the number of components is surprisingly small.
- Package manager: bun
- Linter/Formatter: Biome
- Build: Vite
- AI development: Claude Code
- Version control: Git + Worktree
- CI/CD: GitHub Actions + Blacksmith
- Deploy: Cloudflare Pages
Seven. And bun, Biome, Vite, and Claude Code are the core — the rest are peripheral tools.
The fewer tools you have, the deeper your mastery of each becomes. bun lockfile optimization, Biome rule customization, Vite plugin ordering, Claude Code CLAUDE.md design. Deep understanding of each tool is what enables efficient development and maintenance of ~200 components.
Rather than jumping on every new tool that comes out, try using your current tools one level deeper. In the long run, that's more productive.