Skip to Content
Architecture

Architecture

Numa is structured as a monorepo managed by Turborepo and pnpm workspaces. Applications are independently deployable Next.js apps that share common packages.

Directory structure

numa/ ├── apps/ │ ├── energy/ → energy.numalogistics.com (port 3001) │ ├── gateway/ → gateway.numalogistics.com (port 3000) │ └── docs/ → Internal documentation (port 3005) ├── packages/ │ ├── config/ → Constants, types, platform config, route utilities │ ├── i18n/ → next-intl server config, translation messages │ ├── utils/ → Utility functions, extensions, cn() helper │ ├── api/ → Axios client, TanStack Query helpers, service hooks │ ├── ui/ → Shared UI components (shadcn, G* components, layouts) │ ├── ui-error/ → Lightweight error/not-found components │ └── providers/ → React providers, context, shared hooks └── tooling/ ├── eslint/ → Shared ESLint configuration ├── prettier/ → Shared Prettier configuration └── typescript/ → Shared TypeScript base and Next.js configs

Apps

AppPackage NamePortDescription
Gateway@numa/gateway3000Login, SSO, profile
Energy@numa/energy3001Energy management dashboard

Each app is a Next.js 16 application using the App Router with Turbopack for development.

Packages

Packages are internal libraries that apps import. They are not published to npm — they use workspace:* protocol for linking.

PackagePurpose
@numa/configConstants, types, platform config, route utilities, i18n locales, UI theme
@numa/i18nnext-intl server config, translation messages
@numa/utilsUtility functions, extensions, cn() helper, hooks
@numa/apiAxios client, TanStack Query hook factories, service hooks
@numa/uiShared UI components: shadcn primitives, G-prefixed components, layouts
@numa/ui-errorLightweight error page and not-found components
@numa/providersReact providers (QueryClient, OIDC, essentials), shared hooks

Tooling

Shared configurations consumed by all apps and packages:

PackagePurpose
@numa/eslint-configESLint 9 flat config with Next.js, TypeScript, Prettier
@numa/prettier-configPrettier formatting rules
@numa/typescript-configTypeScript base and Next.js configs

Workspace configuration

pnpm-workspace.yaml

packages: - "apps/*" - "packages/*" - "tooling/*"

turbo.json

Turborepo orchestrates tasks across the monorepo:

  • build — depends on upstream packages (^build), outputs .next/**
  • dev — persistent, no cache
  • lint and type:check — cascade through dependencies
  • clean — no cache

Package linking

Apps declare workspace dependencies in their package.json:

{ "dependencies": { "@numa/config": "workspace:*", "@numa/i18n": "workspace:*", "@numa/api": "workspace:*", "@numa/ui": "workspace:*", "@numa/providers": "workspace:*", "@numa/utils": "workspace:*" } }

Apps must also list shared packages in next.config.ts under transpilePackages so Next.js compiles them:

transpilePackages: [ "@numa/config", "@numa/i18n", "@numa/utils", "@numa/api", "@numa/ui", "@numa/providers", ],

Dependency flow

App (energy, gateway) ├── @numa/providers → React providers, OIDC, essentials │ ├── @numa/api → Axios, TanStack Query │ └── @numa/config ├── @numa/ui → Components, layouts │ ├── @numa/config │ └── @numa/utils ├── @numa/i18n → Translation loading ├── @numa/config → Types, constants, routes └── @numa/utils → Helpers, extensions

Packages should never create circular dependencies. If package A needs something from package B and B already depends on A, use dependency inversion (accept the dependency as a prop or parameter).

Last updated on