Architecture
Every Next.js app in the django-cfg ecosystem follows the same directory layout and naming conventions.
For Django backend app structure see App Design Guide. For reusable package structure see Module Design Guide.
Canonical Directory Structure
app/
├── [locale]/ # next-intl locale wrapper
│ ├── layout.tsx # Root: font + I18nProvider + AppLayout
│ ├── page.tsx # Homepage
│ ├── error.tsx # Error boundary
│ ├── not-found.tsx # 404
│ └── (pages)/ # Route group
│ ├── auth/page.tsx
│ ├── admin/layout.tsx
│ ├── private/ # Auth-guarded routes
│ │ ├── layout.tsx # Auth guard
│ │ ├── profile/
│ │ └── payments/ # Can be self-contained mini-app
│ └── <feature>/ # Public feature routes
│ ├── page.tsx # Server Component (SSR fetch)
│ ├── layout.tsx # Passthrough or providers
│ ├── <Feature>View.tsx # 'use client' wrapper
│ └── [id]/page.tsx # Dynamic routes
│
├── api/ # Next.js API routes
│ ├── contact/route.ts
│ ├── health/route.ts
│ └── og/[data]/route.ts # OG image generation
│
├── _core/ # App-level configuration
│ ├── settings.ts # App name, API URL, media paths
│ ├── metadata.ts # Next.js metadata helper
│ ├── images.ts # Auto-generated image catalog
│ └── globals.css # Tailwind entry point
│
├── _routes/ # Typed route definitions
│ ├── index.ts # Combined routes
│ ├── public.ts # Public routes + URL builders
│ ├── private.ts # Auth-required routes
│ └── admin.ts # Admin routes
│
├── _layouts/ # Thin config wrappers over @djangocfg/layouts
│ ├── AppLayout.tsx # Providers root — wraps BaseApp (mounted once)
│ ├── PublicLayout.tsx # Public shell config (navbar/footer)
│ ├── PrivateLayout.tsx # Private shell config (sidebar/header)
│ └── <Feature>Layout/ # Complex layouts with sub-providers
│ # NOTE: shells are mounted by native route-group layout.tsx files under
│ # (pages)/<type>/ — there's no runtime layout-router. Admin = the
│ # private shell with an admin sidebar config.
│
├── _components/ # Feature UI components (colocated by domain)
│ ├── <domain>/
│ │ ├── cards/
│ │ ├── detail/
│ │ ├── filters/
│ │ ├── list/
│ │ ├── shared/
│ │ └── index.ts # Barrel export
│ └── common/ # Cross-domain shared components
│
├── _lib/ # Business logic layer
│ ├── api/
│ │ ├── BaseClient.ts # Singleton API factory
│ │ ├── server.ts # SSR-safe API factories
│ │ └── generated/ # DO NOT EDIT — auto-generated
│ │ └── <domain>/
│ │ ├── _utils/fetchers/ # Zod-validated fetch functions
│ │ ├── _utils/hooks/ # SWR hooks
│ │ └── _utils/schemas/ # Zod schemas per model
│ ├── contexts/
│ │ ├── <domain>/
│ │ │ ├── <Domain>Context.tsx
│ │ │ ├── types.ts # Single source of truth (re-exports from generated)
│ │ │ └── index.ts
│ │ └── AppProvider.tsx # Composed provider (optional)
│ ├── stores/ # Zustand stores (if needed)
│ ├── hooks/ # Custom hooks
│ ├── utils/ # Pure utility functions
│ └── providers/
│ └── SWRProvider.tsx # Global SWR config + presets
│
├── _ws/ # WebSocket (Centrifugo) — DO NOT EDIT
│ ├── generated/
│ ├── context/
│ └── hooks/
│
└── _views/ # Optional: page-level view componentsNaming Conventions
| Category | Convention | Example |
|---|---|---|
| Directories | Underscore prefix for non-route | _lib/, _components/, _core/ |
| Pages | page.tsx (server), <Name>View.tsx (client) | CatalogView.tsx |
| Contexts | <Domain>Context.tsx + types.ts | MachinesContext.tsx |
| Stores | <domain>Store.ts | timelineStore.ts |
| Hooks | use<Name>.ts | usePlaybackLoop.ts |
| Layouts | <Name>Layout.tsx | DashboardLayout.tsx |
| Barrel exports | index.ts everywhere | Every directory |
TypeScript Path Aliases
Standard aliases from tsconfig.json:
{
"@/*": ["./app/*"],
"@core/*": ["./app/_core/*"],
"@components": ["./app/_components"],
"@lib/*": ["./app/_lib/*"],
"@api/*": ["./app/_lib/api/*"],
"@contexts": ["./app/_lib/contexts"],
"@stores/*": ["./app/_stores/*"],
"@layouts": ["./app/_layouts"],
"@routes": ["./app/_routes"],
"@ws": ["./app/_ws"],
"@i18n/*": ["./i18n/*"]
}Never use relative imports with more than one level (../../). Use aliases instead.
Route Definitions
Routes use defineRoute() from @djangocfg/nextjs/navigation:
export const catalog = defineRoute('/catalog', {
label: 'nav.catalog',
icon: 'Car',
protected: false,
group: 'main',
order: 2,
});
// URL builders alongside routes
export function catalogUrl(filters: Partial<FilterState>): string { ... }
export function vehicleUrl(id: string): string { ... }Three route domains — public, private, admin — aggregated in _routes/index.ts.
next.config.ts Pattern
All apps use createBaseNextConfig from @djangocfg/nextjs/config:
import { createBaseNextConfig } from '@djangocfg/nextjs/config';
import createNextIntlPlugin from 'next-intl/plugin';
const withNextIntl = createNextIntlPlugin('./i18n/request.ts');
const config = createBaseNextConfig({
pwa: { /* Serwist options */ },
i18n: { locales: ['en', 'ru', 'ko'], defaultLocale: 'en' },
});
export default withNextIntl(config);Auto-handles: standalone output (production), transpile packages, dev webpack optimizations, update checks, CORS, and iframe CSP.
Server vs Client Component Split
| Component | Directive | Use for |
|---|---|---|
page.tsx | none (Server) | SSR data fetch, metadata, pass initialData |
layout.tsx | none (Server) | Font, i18n, provider wrapping |
<Feature>View.tsx | 'use client' | Interactive UI, hooks, context consumption |
<Feature>Context.tsx | 'use client' | State + SWR |
<store>.ts | 'use client' (implicit via zustand) | Client state |
Rules
- Every non-route directory uses an underscore prefix (
_lib/,_components/,_core/) - Never use relative imports deeper than one level — use TypeScript aliases (
@lib/,@api/,@contexts) - Never edit
generated/directories manually — regenerate withmake apiormake centrifuge - Route definitions live in
_routes/withdefineRoute()— co-locate URL builder functions with the route they serve - Server Components handle data fetching and metadata — Client Components handle interactivity and hooks
- Every directory exports a barrel
index.tsfor clean import paths