8.5 KiB
8.5 KiB
Directory Structure
This document describes the module organization and folder conventions for the frontend application.
Overview
app/ # Next.js App Router
├── (marketing)/ # Public marketing pages (i18n)
│ └── [locale]/ # Locale-based routing
└── (app)/ # Protected application routes
└── app/ # Main application routes
modules/ # Feature modules
├── [feature]/ # Feature module
│ ├── components/ # UI components
│ ├── hooks/ # Custom hooks
│ ├── context/ # React Context
│ ├── lib/ # Utilities and data transforms
│ └── types/ # Frontend view model types
├── shared/ # Shared components across features
└── ui/ # UI component library
middleware.ts # Authentication & routing middleware
Module Structure
Feature Module Pattern
Each feature module should follow this structure:
modules/dashboard/
├── components/
│ ├── DashboardHeader.tsx
│ ├── StatsCard.tsx
│ ├── ActivityFeed.tsx
│ └── index.ts # Barrel export
├── hooks/
│ ├── useDashboardStats.ts
│ ├── useActivityFeed.ts
│ └── index.ts
├── context/
│ ├── DashboardContext.tsx
│ └── index.ts
├── lib/
│ ├── formatters.ts # Data formatting utilities
│ ├── transformers.ts # API response transformers
│ └── constants.ts # Feature-specific constants
├── types/
│ └── index.ts # View model types
└── index.ts # Public API of the module
Component Organization
components/
├── [ComponentName].tsx # Main component file
├── [ComponentName].test.tsx # Unit tests (if applicable)
└── index.ts # Barrel export
Hooks Organization
hooks/
├── useFeatureData.ts # Data fetching hooks
├── useFeatureActions.ts # Mutation hooks
├── useFeatureState.ts # Local state hooks
└── index.ts
Shared Modules
modules/shared/
Components and utilities shared across multiple features:
shared/
├── components/
│ ├── Layout/ # Layout components
│ ├── Navigation/ # Navigation components
│ ├── DataTable/ # Reusable data tables
│ └── Forms/ # Form components
├── hooks/
│ ├── useUser.ts # Current user hook
│ ├── useOrganization.ts # Organization context
│ └── usePermissions.ts # Permission checks
└── lib/
├── api.ts # API client configuration
└── utils.ts # Shared utilities
modules/ui/
Low-level UI components (design system):
ui/
├── Button/
├── Input/
├── Select/
├── Dialog/
├── Toast/
└── ...
Naming Conventions
Files
| Type | Convention | Example |
|---|---|---|
| Components | PascalCase | UserProfile.tsx |
| Hooks | camelCase with use prefix |
useUserProfile.ts |
| Context | PascalCase with Context suffix |
UserContext.tsx |
| Utilities | camelCase | formatDate.ts |
| Constants | camelCase or SCREAMING_SNAKE_CASE | constants.ts |
| Types | PascalCase | types.ts or UserTypes.ts |
Exports
Use barrel exports (index.ts) for clean imports:
// modules/dashboard/components/index.ts
export { DashboardHeader } from './DashboardHeader';
export { StatsCard } from './StatsCard';
export { ActivityFeed } from './ActivityFeed';
// Usage
import { DashboardHeader, StatsCard } from '@/modules/dashboard/components';
Route-Module Mapping
Routes in app/(app)/ should map to modules in modules/:
app/(app)/
├── dashboard/
│ └── page.tsx -> modules/dashboard/
├── users/
│ ├── page.tsx -> modules/users/
│ └── [id]/
│ └── page.tsx -> modules/users/ (detail view)
├── settings/
│ └── page.tsx -> modules/settings/
└── orders/
├── page.tsx -> modules/orders/
└── [id]/
└── page.tsx -> modules/orders/ (detail view)
Scenario: Organization-Scoped Workspace Routes
1. Scope / Trigger
- Trigger: adding or changing protected app workspace routes, app-shell navigation, breadcrumbs, login redirects, organization switching, or settings pagination links.
- The current workspace URL must include the active organization slug when an organization is selected.
2. Signatures
- Canonical workspace route:
/app/[organizationSlug]/<workspacePath>. - Legacy-compatible route:
/app/<workspacePath>may remain available while old links are migrated. - Shared helpers live in
modules/shared/lib/workspace-routing.ts:getWorkspaceHref(organizationSlug: string | undefined, path?: string): stringgetWorkspacePathFromPathname(pathname: string): stringgetWorkspaceSlugFromPathname(pathname: string): string | undefinedisWorkspacePathActive(pathname: string, itemPath: string): boolean
3. Contracts
navGroupsstores workspace-local paths such as/dashboard,/settings/users, not full/app/...hrefs.AppShellpasses the currentorganization.sluginto navigation and logo links.AppSidebarNav,AppBreadcrumbs, settings search forms, and pagination must generate hrefs withgetWorkspaceHref(...).- The
[organizationSlug]layout must reject slug/session mismatches by redirecting to the active session organization workspace. - Organization slugs cannot use reserved first-level workspace section keys such as
dashboard,settings,elders, orbeds.
4. Validation & Error Matrix
- Missing active organization slug -> helpers fall back to legacy
/app/<workspacePath>. - URL slug differs from
getCurrentAuthContext().organization.slug-> redirect togetWorkspaceHref(activeSlug, "/dashboard"). - New or updated organization slug equals a reserved workspace key -> API returns validation failure.
- A page-level redirect inside protected routes -> use
getWorkspaceHref(context.organization?.slug, targetPath).
5. Good/Base/Bad Cases
- Good: organization switch calls
POST /api/auth/organization, then navigates to the same workspace path under the returned active organization slug. - Base: legacy
/app/dashboardremains renderable for compatibility, but new app-shell links point to/app/{slug}/dashboard. - Bad: hard-code
/app/settings/usersin table forms or breadcrumbs; it drops users out of the organization-scoped workspace. - Bad: infer tenant from
localStorage; active organization remains server-session state.
6. Tests Required
pnpm lintpnpm type-checkpnpm build- Browser assertions:
- login/setup lands on
/app/{activeOrg.slug}/dashboard - sidebar links preserve the active organization slug
- settings search and pagination preserve the active organization slug
- switching organization moves the URL to the new slug while preserving the workspace path
- mismatched
/app/{wrongSlug}/...redirects to the active organization workspace
- login/setup lands on
7. Wrong vs Correct
Wrong
<Link href="/app/settings/users">用户管理</Link>
Correct
<Link href={getWorkspaceHref(organization.slug, "/settings/users")}>用户管理</Link>
Import Path Aliases
Configure in tsconfig.json:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"],
"@/modules/*": ["./modules/*"],
"@/components/*": ["./components/*"],
"@/lib/*": ["./lib/*"]
}
}
}
Best Practices
- Colocation: Keep related files close together
- Single Responsibility: Each module should have one clear purpose
- Explicit Dependencies: Import what you need, avoid implicit globals
- Barrel Exports: Use
index.tsfor public APIs - Private by Default: Only export what needs to be shared
Anti-Patterns to Avoid
- Deeply nested folder structures (max 3-4 levels)
- Circular dependencies between modules
- Mixing feature code with shared utilities
- Importing internal module files directly (use barrel exports)
- Creating "utils" folders that become dumping grounds