Three Start

Why it exists

The problems three-start solves — boilerplate, no model for logic, fragmented community code, and why this isn't R3F.

Three.js is a rendering library. Everything around it, you build yourself — and you build it again in every new project. That "everything around it" is where the trouble starts.

The boilerplate

Spinning up a Three.js scene always starts with the same dozens of lines: renderer, scene, camera, DPR-aware sizing, a ResizeObserver or window listener, a requestAnimationFrame loop, a timer for delta and elapsed time. None of this is interesting, none of it is project-specific, and none of it is standardized across the ecosystem — everyone copies their own preferred snippet.

No model for logic

Once you have a renderer and a scene, you need logic. Things that move, react, initialize, and eventually go away. Three.js has no opinion about how that works — and filling that gap yourself is where most projects start to crack.

1. Where does update come from?

The moment a project has more than one thing happening per frame, this becomes the central question. What you see in the wild:

  • a single animate() function that imports every moving part and calls them in order
  • a growing list of callbacks registered to a hand-rolled event bus
  • per-object closures captured over shared state, triggered from a god-loop
  • framework-shaped abstractions reinvented from scratch in every project

All of these work for a single cube. None of them scale cleanly. The loop becomes the dumping ground where every module's per-frame concerns collide.

2. Lifecycle is the same story.

Every piece of logic also has something to set up on start, and something to clean up when it's done. Without a shared pattern, cleanup is forgotten or re-invented per project. Init code scatters across constructors, factory functions, and ad-hoc setup() calls that may or may not run in the right order. Adding a new system means answering the same questions again from scratch: where does it live, how does it start, where does its update go, what happens on destroy?

3. And then there's control.

A complex object — a player, an enemy, a UI overlay — is often many systems working together: input, animation, physics, sound. All of them need to start together and stop together. Without a standard interface, pausing the game means reaching into each one manually and calling whatever you happened to name the method — disable(), pause(), setSleeping(), mute() — using references you threaded there yourself. Every piece has its own API. Every new system makes the wiring bigger.

Picture this: your player has four systems and you want to pause the game. You call inputManager.disable(), animator.pause(), physicsBody.setSleeping(true), soundEmitter.mute() — all different APIs, all in different places, held together by memory and discipline. Add two more systems next week. Forget to update the pause function. Now you have a bug that only appears after a specific sequence in production.

The pattern doesn't get better on its own — it just grows with the project.

Fragmented community code

Any Three.js example you open — on CodeSandbox, in a Twitter thread, in a repo — is structured differently from the last one. There's no shared mental model, so to understand a demo you read it end to end. This slows the whole ecosystem: harder to learn from, harder to contribute to, harder to onboard into a team.

Why this isn't R3F

React Three Fiber addresses the same organizational problem, and does it well — but by importing the React paradigm, designed for UI, into interactive 3D logic. For people who prefer vanilla + OOP (and there are a lot of us), that trade-off isn't the right one.

three-start is the answer for them. A thin layer that standardizes the bootstrap, the lifecycle, and the way logic is split and wired — without pulling in a rendering paradigm from another domain.

Edit on GitHub

On this page