| name | component-organization |
| description | Maintain consistent file structure - components/ui for base shadcn, components/shared for composed components, pages for routes, always use barrel exports |
Component Organization Guide
This guide establishes the file structure and naming conventions for maintaining a consistent, scalable React TypeScript codebase.
Directory Structure
src/
├── assets/ # Static assets (images, icons, fonts)
│ ├── icons/
│ └── images/
├── components/ # Reusable components
│ ├── ui/ # Base shadcn components (CLI-generated)
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── badge.tsx
│ │ └── index.ts # Barrel export
│ ├── shared/ # Composed app-specific components
│ │ ├── UserProfileCard.tsx
│ │ ├── DataTable.tsx
│ │ └── index.ts # Barrel export
│ └── index.ts # Root barrel export
├── pages/ # Top-level route components
│ ├── Home.tsx
│ ├── About.tsx
│ ├── NotFound.tsx
│ └── index.ts # Barrel export
├── layouts/ # Layout wrappers
│ ├── MainLayout.tsx
│ ├── DashboardLayout.tsx
│ └── index.ts # Barrel export
├── features/ # Feature-based organization (optional)
│ ├── authentication/
│ │ ├── components/
│ │ ├── hooks/
│ │ └── index.ts
│ └── dashboard/
│ ├── components/
│ ├── hooks/
│ └── index.ts
├── hooks/ # Custom React hooks
│ ├── useLocalStorage.ts
│ └── index.ts # Barrel export
├── lib/ # Utilities and helpers
│ └── utils.ts # cn() helper for Tailwind
├── services/ # API and external services
│ ├── api.service.ts
│ └── index.ts # Barrel export
├── constants/ # App-wide constants
│ └── index.ts # APP_NAME, ROUTES, etc.
├── types/ # TypeScript type definitions
│ ├── api.types.ts
│ ├── common.types.ts
│ └── index.ts # Barrel export
└── utils/ # Pure utility functions
└── index.ts
Directory Purposes
components/ui/
Purpose: Base shadcn/ui components generated via CLI
- DO: Add new components using
npx shadcn@latest add [component] - DO: Keep these components pure and unopinionated
- DON'T: Modify these files directly (regenerate if needed)
- DON'T: Add business logic here
Example:
// components/ui/button.tsx (generated by shadcn CLI)
import * as React from "react"
import { cn } from "@/lib/utils"
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
components/shared/
Purpose: Composed, app-specific components built from base UI components
- DO: Create components that combine multiple
ui/components - DO: Add business logic and app-specific styling
- DO: Make these components reusable across the app
- DON'T: Use for one-off components (put those in features or pages)
Example:
// components/shared/UserProfileCard.tsx
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
export interface UserProfileCardProps {
name: string;
role: string;
avatar: string;
}
export function UserProfileCard({ name, role, avatar }: UserProfileCardProps) {
return (
<Card>
<CardHeader>
<img src={avatar} alt={name} className="h-16 w-16 rounded-full" />
<CardTitle>{name}</CardTitle>
<Badge>{role}</Badge>
</CardHeader>
<CardContent>
{/* Additional user details */}
</CardContent>
</Card>
);
}
pages/
Purpose: Top-level route components that render at specific URLs
- DO: Keep components focused on layout and orchestration
- DO: Use descriptive names matching routes (Home.tsx, About.tsx)
- DON'T: Put heavy business logic here (extract to hooks/services)
Example:
// pages/Home.tsx
import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { APP_NAME } from '@/constants';
export function Home() {
const features = [
{
title: 'Vite',
badge: 'Fast',
description: 'Lightning-fast build tool with HMR.',
gradient: 'from-violet-500 to-purple-500',
},
// ... more features
];
return (
<div className="space-y-32">
{/* Hero Section */}
<div className="relative overflow-hidden">
<h1 className="text-6xl font-extrabold">{APP_NAME}</h1>
<p className="text-xl text-gray-600">
A modern React TypeScript starter
</p>
</div>
{/* Features Grid */}
<div className="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
{features.map((feature) => (
<Card key={feature.title}>
<CardHeader>
<CardTitle>{feature.title}</CardTitle>
<Badge>{feature.badge}</Badge>
<CardDescription>{feature.description}</CardDescription>
</CardHeader>
</Card>
))}
</div>
</div>
);
}
layouts/
Purpose: Wrapper components that provide consistent structure across pages
- DO: Handle navigation, headers, footers, sidebars
- DO: Use
<Outlet />from react-router for nested routing - DON'T: Include page-specific content
Example:
// layouts/MainLayout.tsx
import { Link, Outlet, useLocation } from 'react-router-dom';
export function MainLayout() {
const location = useLocation();
const isActive = (path: string) => location.pathname === path;
return (
<div className="min-h-screen bg-white">
{/* Navigation Header */}
<header className="sticky top-0 z-50 border-b bg-white/95">
<nav className="mx-auto max-w-7xl px-8 py-4">
<div className="flex items-center gap-8">
<Link
to="/"
className={`px-4 py-2 font-medium ${
isActive('/') ? 'text-blue-600' : 'text-gray-600'
}`}
>
Home
</Link>
<Link
to="/about"
className={`px-4 py-2 font-medium ${
isActive('/about') ? 'text-blue-600' : 'text-gray-600'
}`}
>
About
</Link>
</div>
</nav>
</header>
{/* Main Content */}
<main className="mx-auto max-w-7xl px-8 py-16">
<Outlet />
</main>
</div>
);
}
lib/
Purpose: Core utilities and helper functions
- DO: Keep the
cn()helper for Tailwind class merging - DO: Add utilities that integrate external libraries
- DON'T: Put pure business logic here (use
utils/instead)
Example:
// lib/utils.ts
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
constants/
Purpose: Application-wide constants and configuration
- DO: Use SCREAMING_SNAKE_CASE for constant names
- DO: Use
as constfor type safety - DON'T: Put environment variables here (use import.meta.env)
Example:
// constants/index.ts
export const APP_NAME = 'Vite React TypeScript Boilerplate';
export const API_BASE_URL =
import.meta.env.VITE_API_BASE_URL || 'http://localhost:3000/api';
export const ROUTES = {
HOME: '/',
ABOUT: '/about',
NOT_FOUND: '*',
} as const;
export const STORAGE_KEYS = {
AUTH_TOKEN: 'auth_token',
USER_PREFERENCES: 'user_preferences',
} as const;
Barrel Exports
Always create index.ts files to enable clean imports and maintain a clear public API.
Pattern 1: Named Exports (Recommended)
// components/ui/index.ts
export { Button } from './button';
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
} from './card';
export { Badge } from './badge';
export { Separator } from './separator';
Pattern 2: Re-export All
// Only use when all exports from a module should be public
export * from './button';
export * from './card';
Pattern 3: Nested Barrel Exports
// components/index.ts - Exposes nested directories
export { Button } from './ui/button';
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
} from './ui/card';
Benefits:
- Clean imports:
import { Button, Card } from '@/components' - Refactoring safety: Move files without breaking imports
- Clear public API: Only exported items are "public"
Naming Conventions
Components
Format: PascalCase
- Files:
Button.tsx,UserProfileCard.tsx,MainLayout.tsx - Components:
export function Button(),export function UserProfileCard()
Utilities and Services
Format: camelCase or kebab-case
- Files:
utils.ts,api.service.ts,useLocalStorage.ts - Functions:
export function cn(),export function useLocalStorage()
Directories
Format: kebab-case
components/ui,components/shared,pages,layouts
Constants
Format: SCREAMING_SNAKE_CASE
APP_NAME,API_BASE_URL,STORAGE_KEYS
Types and Interfaces
Format: PascalCase with descriptive suffix
UserProfileCardProps,ApiResponse,AuthState
Decision Flowchart: Where to Place New Components
┌─────────────────────────────────────┐
│ Need to create a new component? │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Is it a shadcn/ui base component? │
└──────────────┬──────────────────────┘
│
┌─────┴─────┐
│ │
YES NO
│ │
▼ ▼
┌─────────┐ ┌──────────────────────────────────┐
│ Use CLI │ │ Is it used in 2+ different │
│ to add │ │ features/pages? │
│ to ui/ │ └──────────────┬───────────────────┘
└─────────┘ │
┌─────┴─────┐
│ │
YES NO
│ │
▼ ▼
┌──────────────────┐ ┌─────────────────────┐
│ Does it compose │ │ Is it a full page │
│ multiple ui/ │ │ route? │
│ components? │ └──────────┬──────────┘
└────────┬─────────┘ │
│ ┌────────┴────────┐
┌─────┴─────┐ │ │
│ │ YES NO
YES NO │ │
│ │ ▼ ▼
▼ ▼ ┌────────┐ ┌──────────────┐
┌──────────┐ ┌────────┐│ pages/ │ │ Is it part │
│ shared/ │ │ hooks/ ││ │ │ of a cohesive│
│ │ │ or │└────────┘ │ feature? │
└──────────┘ │ utils/ │ └──────┬───────┘
└────────┘ │
┌────────┴────────┐
│ │
YES NO
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ features/ │ │ Colocate with│
│ [feature]/ │ │ parent │
│ components/ │ │ component │
└──────────────┘ └──────────────┘
When to Create New Components
DRY Principle: Reusability > 2x Usage
If you're copying the same JSX structure more than twice, extract it into a component.
Before:
// Multiple pages with duplicated card structure
export function Dashboard() {
return (
<Card>
<CardHeader>
<CardTitle>Stats</CardTitle>
</CardHeader>
<CardContent>{/* ... */}</CardContent>
</Card>
);
}
export function Profile() {
return (
<Card>
<CardHeader>
<CardTitle>User Info</CardTitle>
</CardHeader>
<CardContent>{/* ... */}</CardContent>
</Card>
);
}
After:
// components/shared/StatsCard.tsx
export function StatsCard({ title, children }) {
return (
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent>{children}</CardContent>
</Card>
);
}
Complexity: Component Logic > 50 Lines
If a component or section exceeds 50 lines, consider extracting it.
Before:
export function Dashboard() {
return (
<div>
{/* 80 lines of user profile UI */}
<div className="user-profile">
{/* Complex profile rendering */}
</div>
{/* 60 lines of statistics UI */}
<div className="statistics">
{/* Complex stats rendering */}
</div>
</div>
);
}
After:
export function Dashboard() {
return (
<div>
<UserProfile />
<Statistics />
</div>
);
}
Composition: Building from Multiple Base Components
When combining 3+ base UI components, create a composed component.
// components/shared/UserCard.tsx - Composes Card, Badge, Button
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
export function UserCard({ user }) {
return (
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle>{user.name}</CardTitle>
<Badge>{user.role}</Badge>
</div>
</CardHeader>
<CardContent>
<p>{user.email}</p>
<Button variant="outline">View Profile</Button>
</CardContent>
</Card>
);
}
Domain Logic: App-Specific Behavior
When adding business logic, API calls, or state management, extract to a component.
// components/shared/ProductList.tsx - Contains domain logic
import { useEffect, useState } from 'react';
import { fetchProducts } from '@/services/api.service';
import { Card } from '@/components/ui/card';
export function ProductList() {
const [products, setProducts] = useState([]);
useEffect(() => {
fetchProducts().then(setProducts);
}, []);
return (
<div className="grid gap-4">
{products.map(product => (
<Card key={product.id}>
{/* Product display */}
</Card>
))}
</div>
);
}
Real-World Examples from This Project
Example 1: UI Component Barrel Export
// src/components/ui/index.ts
export { Button } from './button';
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
} from './card';
export { Badge } from './badge';
export { Separator } from './separator';
Example 2: Page Component with Feature Imports
// src/pages/Home.tsx
import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { APP_NAME } from '@/constants';
export function Home() {
// Page orchestrates UI components
return (
<div className="space-y-32">
<h1>{APP_NAME}</h1>
{/* Feature showcase using composed components */}
</div>
);
}
Example 3: Layout with Router Integration
// src/layouts/MainLayout.tsx
import { Link, Outlet, useLocation } from 'react-router-dom';
export function MainLayout() {
const location = useLocation();
return (
<div className="min-h-screen">
<header className="sticky top-0">
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
</header>
<main>
<Outlet /> {/* Pages render here */}
</main>
</div>
);
}
Example 4: Constants with Type Safety
// src/constants/index.ts
export const APP_NAME = 'Vite React TypeScript Boilerplate';
export const ROUTES = {
HOME: '/',
ABOUT: '/about',
NOT_FOUND: '*',
} as const;
// Usage in pages
import { ROUTES } from '@/constants';
<Link to={ROUTES.HOME}>Home</Link>
Quick Reference Checklist
- Created barrel export
index.tsfor new directory? - Used PascalCase for component files and exports?
- Used kebab-case for directory names?
- Added shadcn components via CLI to
components/ui/? - Placed composed components in
components/shared/? - Put route components in
pages/directory? - Exported layouts from
layouts/directory? - Used SCREAMING_SNAKE_CASE for constants?
- Added TypeScript types for component props?
- Verified component is reused 2+ times before creating?
- Extracted complex logic (>50 lines) to separate component?
- Used
@/path alias for imports?
Anti-Patterns to Avoid
DON'T modify shadcn UI components directly:
// ❌ Bad: Editing ui/button.tsx manually
// If you need customization, create a wrapper in shared/
DON'T skip barrel exports:
// ❌ Bad: Direct imports without barrel
import { Button } from '@/components/ui/button';
import { Card } from '@/components/ui/card';
// ✅ Good: Use barrel exports
import { Button, Card } from '@/components/ui';
DON'T create one-off components in shared/:
// ❌ Bad: Single-use component in shared/
// components/shared/HomepageHeroSection.tsx (only used once)
// ✅ Good: Keep in the page or feature
// pages/Home.tsx (inline) or features/homepage/components/
DON'T mix concerns in constants:
// ❌ Bad: Mixing types and runtime values
export const ENDPOINTS = {
users: '/api/users',
auth: '/api/auth',
};
export type User = { id: string; name: string };
// ✅ Good: Separate constants and types
// constants/index.ts - runtime values
// types/index.ts - type definitions
Remember: Good organization makes scaling easy. When in doubt, follow the principle: "Start simple, extract when you repeat."