Starting a new project
Sometimes you will not maintain a project - bumping versions in package.json and testing every corner of the app - but build something brand new. From scratch! Here's some pointers to get you started without drowning in choices.
Starting fresh
Starting fresh gives you many options on how to chose your stack, but the amount of options can be overwhelming. Here's some pointers and advice navigating the landscape. Going modern, within reason, is the general rule of thumb.
Internal app: Next.JS
Many have used Next.JS for creating purely internal apps. This handles routing and many other things in a satisfying way when nothing needs to be exposed to the public. Custom facing apps still need to stick to the CMS and/or Application Portal-way of going online.
Repo structure: Monorepo
It is rarely a mistake to put multiple tightly coupled apps into a monorepo, instead of spreading it across multiple repos. We have a dedicated guide on Monorepos.
App/Project Structure
Bulletproof React is a set of rules on how to structure an app. This architecture gives you a scalable, feature‑oriented folder structure. It encourages clear boundaries, reusable patterns, and maintainable conventions as the codebase grows.
Engine: bun
Bun is a modern JavaScript runtime that replaces several legacy Node.js tools at once. It’s fast, compact, and ships with a built‑in test runner, bundler, and package manager. Node compatibility is extremely strong.
Build Tool: Vite
Vite offers a fast, modern development environment with instant HMR and simple configuration. It has become the default choice for React, Vue, and other frameworks thanks to its speed and developer experience.
Linting & Formatting
Biome on its own - or the pairing of Oxlint and Oxfmt - these Rust‑based tools provide a faster, simpler alternative to the traditional ESLint + Prettier setup. They reduce configuration overhead and dramatically speed up linting and formatting. Not to mention they're a built for modern web development, not for how it was ages ago.
Data Fetching / Server State
TanStack Query is the go‑to solution for managing server state. It handles caching, background updates, retries, and synchronization automatically, letting you remove a lot of boilerplate and avoid misusing global state for API data.
Client State
Zustand keeps UI‑only state lightweight and predictable. It avoids the complexity of large global stores and pairs naturally with TanStack Query, which handles all server‑derived data.
Testing: Vitest + Playwright
Playwright is usually the preferred option, compared to bigger more mature frameworks. Combined with Vitest usually gives you a lot of coverage fast.
Guiding Principles
Keep the architecture grounded in proven engineering ideas:
-
KISS — avoid unnecessary abstractions
-
YAGNI — don’t build what you don’t need yet
-
DRY — reduce duplication without over‑engineering
-
Single Responsibility — keep modules focused
-
Type Safety — lean on TypeScript
-
Pragmatic Performance — optimize only when it matters
Last but not least
And do follow our 27 principles for building great web apps. Good luck!