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, with Client holding shared request/response contracts and Connectors holding 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 via next-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 main publishes 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.