C.W.K.
Stream
Lesson 07 of 07 · published

Project Structure That Survives Growth

~14 min · architecture, file-structure, organization

Level 0React Novice
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
There is no official React file structure. That's a feature. The cost is that you have to choose — and the wrong choice rots fast.

The two organizing axes

Every React project picks (consciously or not) along two axes:

  1. By type vs by feature. Type-first puts all hooks in hooks/, all components in components/, all utilities in lib/. Feature-first puts a feature's hooks, components, and utilities together in features/chat/, features/auth/, etc.
  2. Flat vs deep. Flat is one or two levels of nesting; deep can go five or six.

Type-first wins early (easier to find things by category). Feature-first wins later (easier to delete or extract a whole feature). The transition point is somewhere around 50 components.

The cwkPippa frontend layout

cwkPippa's frontend is feature-first, two levels deep:

src/
  App.tsx              # state lifting + route mounting
  main.tsx             # entry point
  index.css            # Tailwind v4 + theme tokens
  components/
    chat/              # InputArea, MessageList, MessageItem
    sidebar/           # ConversationList, FolderTree
    council/           # Council UI surface
    admin/             # admin panels
    settings/          # settings UI
  hooks/               # useChat, useConversations, useHeartbeat
  lib/                 # api.ts, formatters, constants
  types/               # shared TS types

Notice: components are grouped by feature (chat/, sidebar/, council/), but hooks and types stay type-first because they're shared. That hybrid is normal — pick the right axis per directory.

The state-lifting question

App.tsx in cwkPippa is the state-lifting root: top-level state lives there, child components receive props and callbacks. Some projects push state into Zustand stores or Context providers instead. There's no correct answer — but consistency matters. Pick one and stick.

Path aliases

Long relative imports (../../../components/chat/MessageList) are a smell. Vite supports path aliases via tsconfig.json's paths field. Set up @/* to point at src/* and your imports become @/components/chat/MessageList — readable and refactorable.

The folder you'd want to find this file in. When you create a new file, ask: 'If I were looking for this file in three months, where would I look first?' Put it there. If your answer is 'I'd grep,' your structure has already broken.

Code

tsconfig.app.json — path alias setup·json
{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }
  }
}
vite.config.ts — matching Vite resolver·ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tailwindcss from "@tailwindcss/vite";
import path from "node:path";

export default defineConfig({
  plugins: [react(), tailwindcss()],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src"),
    },
  },
});
Before / after — readable imports·tsx
// Before: brittle, breaks on file moves
import { MessageList } from "../../../components/chat/MessageList";
import { useChat } from "../../../hooks/useChat";

// After: stable across moves
import { MessageList } from "@/components/chat/MessageList";
import { useChat } from "@/hooks/useChat";

External links

Exercise

Set up the @/* path alias in both tsconfig.app.json and vite.config.ts. Move your App.tsx and a sample component into directories that follow the feature-first or type-first axis. Refactor at least one import to use the alias. Then deliberately move the component file to a different directory and watch the alias-based import keep working while a relative import would have broken.
Hint
If imports break in the editor but the dev server runs, only Vite has the alias. If they work in the editor but fail at runtime, only tsconfig has it. Both must match.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.