Pages & Layouts
Numa uses the Next.js App Router with a [lang] segment for locales. Route groups separate public and private areas.
High-level structure
app/
├── layout.tsx # Root: font, ThemeVariables, NextIntl, providers, Toaster
├── error.tsx # Root error boundary
├── [lang]/
│ ├── layout.tsx # Locale-aware wrapper
│ ├── not-found.tsx # 404 page
│ ├── error.tsx # Locale-scoped error boundary
│ ├── [...404]/page.tsx # Catch-all 404
│ └── (app)/
│ ├── layout.tsx # App-level providers / shell
│ ├── page.tsx # App home
│ ├── (public)/(auth)/ # Login, register, set-password
│ └── (private)/ # Requires session + /me
│ ├── layout.tsx # Private shell: fetch /me, EssentialsProvider
│ ├── (energy)/ # Energy dashboard pages
│ ├── (operations)/ # Operations placeholder
│ ├── (finance)/ # Finance placeholder
│ └── (shared)/ # Shared profileRoute groups
| Group | Purpose |
|---|---|
(public) | Unauthenticated users; auth forms, marketing entry |
(private) | Token required; platform segments may be further restricted |
Root layout (app/layout.tsx)
The root layout sets up:
- Space Grotesk font via
next/font - ThemeVariables — CSS variables from
@numa/config/ui/theme - GProviders — TanStack Query, essentials sync, OIDC, toasts
- NextIntlClientProvider — messages for the active locale
Private layout
app/[lang]/(app)/(private)/layout.tsx wraps private routes in the PrivateShell, which:
- Redirects to login if there is no access token
- Fetches user essentials server-side when possible
- Falls back to
HydrateEssentialsFromClientif server fetch fails
Page naming convention
Each route folder typically contains:
| File | Purpose |
|---|---|
page.tsx | Next.js route entry (thin, imports view) |
page-view.tsx | Main page content (client component) |
filter-modal.tsx | Filter UI for list pages |
funcs.ts | Page-specific helper functions |
hooks.ts | Page-specific custom hooks |
Example — Energy clients:
apps/energy/app/[lang]/(app)/clients/
├── page.tsx # Route entry
├── page-view.tsx # Client component with list UI
├── filter-modal.tsx # Filter modal
├── funcs.ts # Helper functions
└── new-client-modal.tsx # Create modalEdge routing (proxy.ts)
Each app uses a proxy.ts file (instead of middleware.ts) to handle:
- Locale resolution — redirects to locale-prefixed paths
- Authentication — redirects unauthenticated users to login
- Platform permissions — ensures users have access to the platform
- Cross-app redirects — uses
CROSS_APP_URLSfor inter-app navigation
Adding a page
- Create the route folder under the appropriate group (e.g.,
app/[lang]/(app)/my-route/) - Add
page.tsxas the route entry point - Create
page-view.tsxfor the main content (use"use client"for interactive pages) - For energy list screens, use
GEnergyPagefor consistent layout - See Adding a Page for a full walkthrough
Last updated on