Skip to Content
Pages & Layouts

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 profile

Route groups

GroupPurpose
(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:

  1. Redirects to login if there is no access token
  2. Fetches user essentials server-side when possible
  3. Falls back to HydrateEssentialsFromClient if server fetch fails

Page naming convention

Each route folder typically contains:

FilePurpose
page.tsxNext.js route entry (thin, imports view)
page-view.tsxMain page content (client component)
filter-modal.tsxFilter UI for list pages
funcs.tsPage-specific helper functions
hooks.tsPage-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 modal

Edge 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_URLS for inter-app navigation

Adding a page

  1. Create the route folder under the appropriate group (e.g., app/[lang]/(app)/my-route/)
  2. Add page.tsx as the route entry point
  3. Create page-view.tsx for the main content (use "use client" for interactive pages)
  4. For energy list screens, use GEnergyPage for consistent layout
  5. See Adding a Page for a full walkthrough
Last updated on