How do I Structure Projects for Scalability



This content originally appeared on DEV Community and was authored by Aleksandr Ryzhikov

When the project is small, we can easily “dump everything into one folder” and work. But as the application grows, the chaos in the structure begins to slow down development, complicate support, and hinder new team members.

In this article, I’ll show you how I structure frontend projects to be scalable, predictable, and convenient for teamwork.

Principles

Before moving on to the structure, I always adhere to three rules::

  1. Explicit is better than implicit — one additional folder is better than magic with obscure imports.
  2. Features are more important than layers — instead of “/components”, “/services”, I try to highlight functional modules.
  3. Scalability from day one — even if the project is small, the structure should allow for growth without restructuring.

Basic project structure

src/
├── app/                    # Application Level configuration
│   ├── providers/          # Context providers, themes, routers
│   ├── store/              # Global status
│   └── config/             # Constants, settings
├── entities/               # Entities (User, Product, Todo, etc.)
├── features/               # Business features (Login, Search, Cart, etc.)
├── pages/                  # Pages (UI level routing)
├── shared/                 # Reusable utilities, UI components, helpers
└── index.TSX # Entry Point

Example: entities

The entities/ stores models, APIs, and minimal components. For example, entities/todo/:

entities/
└── todo/
    ├── api/
    │ └── todoApi.ts # CRUD operations
    ├── model/
    │   ├── types.ts # Entity Types
    │   └── store.ts # Zustand/Redux status
    └── ui/
        └── TodoItem.tsx # Basic UI Component
// entities/todo/api/todoApi.ts
export const fetchTodos = async () => {
  const res = await fetch("/api/todos");
  return res.json();
};

Example: feature

A feature combines several entities to solve a problem. For example, features/TodoList/:

features/
└── todoList/
    ├── ui/
    │   └── TodoList.tsx
    └── model/
        └── hooks.ts # Local Hooks
// features/todoList/ui/TodoList.tsx
import { useEffect, useState } from "react";
import { fetchTodos } from "@/entities/todo/api/todoApi";
import { TodoItem } from "@/entities/todo/ui/TodoItem";

export function TodoList() {
  const [todos, setTodos] = useState([]);

  useEffect(() => {
    fetchTodos().then(setTodos);
  }, []);

  return (
    <div>
      {todos.map((todo) => (
        <TodoItem key={todo.id} {...todo} />
      ))}
    </div>
  );
}

Example: a Page

Pages collect features and entities into a ready-made screen.

pages/
└── home/
    └── HomePage.tsx
// pages/home/HomePage.tsx
import { TodoList } from "@/features/todoList/ui/TodoList";

export function HomePage() {
  return (
    <main>
      <h1>My tasks</h1>
      <TodoList />
    </main>
  );
}

Shared — always at hand

shared/ contains everything that does not depend on a specific entity or feature:

shared/
├── ui/   # Buttons, inputs, mods
├── lib/  # Utilities, helpers
├── api/  # Basic HTTP client
// shared/api/http.ts
export async function http<T>(url: string, options?: RequestInit): Promise<T> {
  const res = await fetch(url, options);
  if (!res.ok) throw new Error("Network error");
  return res.json();
}

Why it works

  • It’s easy for a new developer to navigate: Essentials → Features → Page.
  • The code can be scaled: new features are added without revising the entire architecture.
  • shared/ remains compact and predictable, without leaking business logic.

Conclusion

This structure has grown from real projects, from small pet projects to applications with dozens of developers. It’s not the only correct one, but it avoids the “spaghetti code” and makes it easier to maintain.

And how do you structure projects? Share your experience in the comments.👇


This content originally appeared on DEV Community and was authored by Aleksandr Ryzhikov