Skip to Content
FrontendArchitecture

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 components

Naming Conventions

CategoryConventionExample
DirectoriesUnderscore prefix for non-route_lib/, _components/, _core/
Pagespage.tsx (server), <Name>View.tsx (client)CatalogView.tsx
Contexts<Domain>Context.tsx + types.tsMachinesContext.tsx
Stores<domain>Store.tstimelineStore.ts
Hooksuse<Name>.tsusePlaybackLoop.ts
Layouts<Name>Layout.tsxDashboardLayout.tsx
Barrel exportsindex.ts everywhereEvery 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

ComponentDirectiveUse for
page.tsxnone (Server)SSR data fetch, metadata, pass initialData
layout.tsxnone (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

  1. Every non-route directory uses an underscore prefix (_lib/, _components/, _core/)
  2. Never use relative imports deeper than one level — use TypeScript aliases (@lib/, @api/, @contexts)
  3. Never edit generated/ directories manually — regenerate with make api or make centrifuge
  4. Route definitions live in _routes/ with defineRoute() — co-locate URL builder functions with the route they serve
  5. Server Components handle data fetching and metadata — Client Components handle interactivity and hooks
  6. Every directory exports a barrel index.ts for clean import paths
Last updated on