1The Problem With One Big File
When you start a game, everything in one file feels fine. Two hundred lines of code is easy to navigate. But games grow. Enemy logic gets more complex. You add UI. You add audio. You add a leaderboard. By 800 lines, a single file becomes a liability: scrolling endlessly to find the function you need, accidental variable name collisions, no clear place to put new things.
The solution is not clever engineering. It is simple separation: one file per concern. Each file knows exactly what it is responsible for, and nothing else.
A file should have one reason to change. player.js changes when player movement changes. enemies.js changes when enemy behavior changes. state.js changes when scoring rules change. When you know exactly which file to open for a given problem, you are working with good structure.
2The Reference Structure
Below is the full recommended file structure for a Three.js/Vite microgame. This is not the only valid structure โ it is a structure that has proven to work for games of this scale without over-engineering.
โโโ index.html โ canvas, HUD elements, overlay divs
โโโ vite.config.js โ dev server, build config
โโโ package.json
โโโ README.md โ describe the game; helps on GitHub
โโโ public/
โ โโโ favicon.ico
โโโ src/
โโโ main.js โ entry point: init + game loop
โโโ scene.js โ renderer, camera, lights, floor
โโโ player.js โ player mesh, input, movement
โโโ enemies.js โ enemy spawn, update, pooling
โโโ state.js โ score, lives, timer, game events
โโโ ui.js โ HUD update, overlay show/hide
โโโ utils/
โโโ math.js โ distance, clamp, lerp helpers
3What Each File Does
index.html โ The Shell
The HTML file is intentionally thin. Its only job is to provide the DOM structure: a canvas element, HUD divs (score, timer, lives), and an overlay div for start/end screens. All styling goes in a linked CSS file. All logic goes in the JS modules. HTML stays as a dumb skeleton.
<canvas id="c"></canvas> <!-- Three.js renders here --> <div id="hud"> <span id="score">SCORE: 0</span> <span id="timer">60</span> <span id="lives">โฅโฅโฅ</span> </div> <div id="overlay"> <!-- start / game-over / win screen --> <h1>My Game</h1> <button id="start-btn">PLAY</button> </div> <script type="module" src="src/main.js"></script>
main.js โ The Conductor
main.js imports everything and owns the game loop. It does not contain the logic for any system โ it just calls the update functions of each system in the correct order. If your main.js is longer than 80 lines, you probably have logic there that belongs somewhere else.
scene.js โ The Stage
Renderer, camera, lights, floor, and the resize handler. Created once at startup. Exported so other modules can add objects to the scene. Nothing in scene.js knows about gameplay โ it is pure Three.js setup.
player.js โ One Actor
The player mesh, its starting position, the key-state tracker, and the updatePlayer() function. It also exports helper state that other modules need: player.position for collision checks, and the setInvincible() function for when enemies score a hit.
enemies.js โ A System
The enemy array, the spawnEnemy() factory, and updateEnemies(). This module does not know about the player, the score, or the timer โ it only manages enemy meshes. Collision logic (which involves both the player and enemies) lives in state.js, where it can affect the score.
state.js โ The Rules
The score, lives, time remaining, and the running flag. This is the source of truth for game state. It also owns checkCollisions() because collisions produce game state changes (score increases, lives decrease). It fires endGame() when conditions are met.
ui.js โ The Display
All DOM manipulation lives here: updating the score text, changing the timer color when it turns critical, toggling the overlay visibility, and injecting the win/lose message. Separating this means your game logic never touches the DOM directly โ a clean separation that makes both sides easier to change.
4When to Break the Rules
The structure above is a guideline, not a religion. For a game under 300 lines total, two or three files is probably the right answer. The goal is to reduce cognitive load โ the number of things you have to hold in your head at once to make a change. If the structure is adding complexity rather than reducing it, simplify.
The most common mistake is over-engineering: creating an elaborate class hierarchy, an event bus, a component system, a service locator pattern. These are solutions to problems that only exist at a scale you have not reached yet. Start simple. Extract files when a file gets long enough that you have to scroll to find things. That is the right time, and no earlier.
Open any file in your project and ask: can I describe what this file does in one sentence? If the answer is "it handles some game stuff," that file needs to be split. If the answer is "it manages enemy spawning and movement," that is good structure. The ability to answer in one sentence is the measure.