← Back to articles
February 20269 min read

How to Structure a Next.js Project for Long-Term Maintainability

Folder structure, feature modules, naming conventions, API layers, and scaling patterns from MVP to production. Practical guide for Next.js dashboard and Web3 projects.

Next.jsArchitectureTypeScriptBest Practices

Why folder structure matters

A messy project structure slows every future feature. Developers waste time finding files, duplicate logic across folders, and break imports when refactoring. A clear structure scales from a weekend MVP to a production app with multiple contributors — without reorganization.

Recommended folder structure

src/
  app/                          # Next.js App Router pages
    layout.tsx
    page.tsx
    dashboard/
      page.tsx
      layout.tsx
    api/
      balances/route.ts
  components/
    ui/                         # Shared primitives (Button, Skeleton, Modal)
    layout/                     # Header, Footer, Sidebar
  features/                     # Feature modules (self-contained)
    portfolio/
      components/
      hooks/
      types.ts
    wallet/
      components/
      hooks/
  hooks/                        # Shared hooks
  lib/                          # Config, clients, utilities
    wagmi-config.ts
    api-client.ts
    format.ts
    constants.ts
  types/                        # Global TypeScript types
  data/                         # Static data, article content

Components, hooks, utils, services, types

**components/ui/** — Reusable UI primitives with no business logic. Button, Input, Skeleton, Modal, DataTable.

**components/layout/** — Page structure components. Header, Footer, Sidebar, PageContainer.

**features/** — Self-contained feature modules. Each feature owns its components, hooks, and types. A feature should be deletable without breaking unrelated code.

**hooks/** — Shared hooks used across features. useDebounce, useMediaQuery, useLocalStorage.

**lib/** — Configuration, API clients, utility functions, constants. No React components here.

**types/** — Global TypeScript interfaces and type aliases.

API and data layer

// lib/api-client.ts — centralized HTTP layer
type RequestOptions = { params?: Record<string, string>; signal?: AbortSignal };

async function request<T>(path: string, options?: RequestOptions): Promise<T> {
  const url = new URL(path, '/api');
  if (options?.params) {
    Object.entries(options.params).forEach(([k, v]) => url.searchParams.set(k, v));
  }
  const res = await fetch(url, { signal: options?.signal });
  if (!res.ok) throw new Error(`API error: ${res.status}`);
  return res.json();
}

export const api = {
  portfolio: {
    get: (address: string) => request<Portfolio>(`/portfolio/${address}`),
  },
  prices: {
    get: (ids: string[]) => request<PriceMap>('/prices', { params: { ids: ids.join(',') } }),
  },
};

Feature-based organization

When a feature grows beyond 2 components and 1 hook, give it its own folder:

features/transactions/
  components/
    TransactionTable.tsx
    TransactionRow.tsx
    TransactionFilters.tsx
  hooks/
    useTransactions.ts
    useTransactionExport.ts
  types.ts
  index.ts              # Public API — only export what other features need
// features/transactions/index.ts
export { TransactionTable } from './components/TransactionTable';
export { useTransactions } from './hooks/useTransactions';
export type { Transaction } from './types';

Naming conventions

Files: PascalCase for components (PortfolioSummary.tsx), camelCase for hooks (usePortfolio.ts) and utilities (format.ts). Folders: kebab-case or lowercase (features/portfolio/, components/ui/). Types: PascalCase interfaces (TokenPosition, ApiResponse). Constants: SCREAMING_SNAKE_CASE (SUPPORTED_CHAINS, CACHE_TTL).

Scaling from MVP to production

**MVP (1–2 weeks):** Flat structure is fine. components/, hooks/, lib/, app/.

**Growing (1–3 months):** Introduce features/ folder. Extract shared UI into components/ui/. Centralize API calls in lib/api-client.ts.

**Production (3+ months):** Feature modules with index.ts exports. Shared types in types/. API routes organized by domain. CI linting rules for import boundaries.

FAQ

**Monorepo or single repo?** Single repo for most Web3 dashboards. Monorepo only if you have separate packages (shared UI library, SDK).

**Where do API routes go?** app/api/ following the same domain structure as features. app/api/portfolio/[address]/route.ts.

**Should I use barrel exports (index.ts)?** Yes for feature modules (features/portfolio/index.ts). No for components/ui/ — direct imports are clearer and tree-shake better.

**How do I enforce structure?** ESLint import rules. TypeScript path aliases in tsconfig.json (@/features/*, @/components/*, @/lib/*).

Want to work together? I build Web3 dashboards and DeFi interfaces.