Tech Stack
What metrd is built with.
metrd is a monorepo of three deployable pieces plus two shared packages, built and released independently.
App
The user-facing web app: the dashboard for setting things up, and the monitor that shows a live display.
- Next.js 16 (App Router) with React 19
- TypeScript, strict mode
- SignalR (
@microsoft/signalr) for the live connection that drives the display - Vexa (
Vexa/Library), our own UI framework, for the dashboard's templates, components, forms and formatting - Generated API client (
packages/api-client), produced by parsing the API's C# controllers and SignalR hubs directly - no separate schema to keep in sync
API
The backend service.
- ASP.NET Core on .NET 10
- Entity Framework Core with Npgsql for PostgreSQL
- Layered as
Service→Business→Data, withClientholding shared request/response contracts andConnectorsholding the clients for each outside service (SolaX, Solcast, Easee), kept dependency-free from the rest of the API - Custom Roslyn analysers (
Metrd.API.Analysers) enforce project-specific conventions at compile time, with code fixes provided where practical - MSTest and Moq for unit tests, plus dedicated convention tests that check structural and naming rules across every project
Site
This documentation and marketing site.
- Next.js 16 with TypeScript
- Docs are plain Markdown files under
Site/docs/, rendered vianext-mdx-remote, with the sidebar generated automatically from the folder structure
Infrastructure
- PostgreSQL 17 as the primary database
- Docker images for the API and App, published to GitHub Container Registry
- GitHub Actions for PR checks, image publishing, and a Docker Compose smoke test on every change to the stack
- Merging a version bump to
mainpublishes new images and a GitHub release
Performance principles
metrd is built to run on light hardware, not a beefy server, so keeping the API and database frugal is a first-class concern rather than an afterthought:
- Reference data that rarely changes (units, metrics, currencies, display layouts) is served from an in-process hybrid cache rather than re-queried on every request.
- The live display works from readings held in memory rather than querying the database on every update.
- Readings can be bucketed for retention (see Retention) so history doesn't grow without bound and older queries scan less data.
- The layered architecture keeps data access confined to repositories, which makes it straightforward to see and control exactly where a database round trip happens.
The guiding rule: every request and every trip to the database counts, so avoid the ones you don't need.
See Coding Standards & Contributing for how these pieces fit together day to day.