Skip to content
Back to writing
6 min read
#energy-tech #fintech-infrastructure

Engineering at Upgrid

Next.js, Supabase, Linear, Steep, PostHog, Better Stack. A tour of the Upgrid stack — how a small team ships Swiss energy billing without a microservice estate.

From the outside, Upgrid looks like a Swiss energy SaaS: a join flow, a dashboard, an invoice. Underneath that is a Turborepo on Vercel and Supabase, work in Linear, product metrics in Steep, funnels and flags in PostHog, and heartbeats in Better Stack.

The company story is in Building Upgrid. This is the stack as of August 2026. For the last time I wrote one of these, at a very different scale, see Engineering at N26.

The shape of the system

Stack at a glance (2026)
01

Product surfaces

Localized marketing (de, en, fr), consumer dashboard, operator console, partner portal, admin, and a public API. One codebase, different permissions.

Next.jsi18nRLS
02

Application

Next.js 16 App Router, React 19, TypeScript. Server Components and server actions; route handlers for crons and webhooks.

RSCServer ActionsHono
03

Data

One Supabase Postgres with row-level security. Kysely for multi-table writes; Supabase client for RLS-backed reads.

PostgreSQLSupabaseKysely
04

Delivery

Vercel for hosting, GitHub Actions for CI, fifteen Vercel crons for background work. Feature flags and monitors defined in the repo (CDKTF).

VercelGitHub ActionsCDKTF
05

How we run it

Linear for issues. Steep for governed business metrics. PostHog for analytics and flags. Better Stack and Sentry for uptime and errors. Slite for ops narrative.

LinearSteepPostHogBetter Stack
7 apps
~70 workspace packages
570
SQL migrations in the product repo
15
Vercel cron jobs
3
Locales — de, en, fr

One Turborepo. Splitting it into services later would add coordination, not capacity.

Monorepo on purpose

Seven apps (web, api, company-brain, and a few internal tools) plus ~70 workspace packages share one Postgres schema and one CI pipeline. Domain logic — consumption allocation, SDAT import, energy billing, the Bexio client — lives in shared packages imported by both the Next.js app and the public API. Multi-tenancy is RLS: personal accounts, team workspaces, operator accounts, and sub-brand partners.

Why a monorepo instead of the ~230-service estate I came from is in Building Upgrid. Billing, allocation, and ingestion share types and tables. That coupling is the product.

What the product computes

Swiss energy law has three community models — ZEV, vZEV, LEG — and we run them as separate workflows, not one flow with three labels. Prospects are contract intents; they become live contracts only after activation.

Metering is the input. SDAT is the format Swiss grid operators use; the interval is fifteen minutes.

An hourly cron pulls files from the Swisseldex hub over SFTP into Supabase Storage. A five-minute cron parses them into monthly-partitioned readings. vZEV allocation splits solar across two pools on those fifteen-minute slots. Community invoices are a separate pipeline from Stripe subscriptions: draft → approved → issued → Swiss QR-bill PDF → optional Bexio sync.

The domain is the constraint. The rest of this piece is the stack we use to ship it.

Linear

Work is tracked in Linear (UPG-*). Architecture docs, VAT decisions, ingestion specs, and billing-hardening milestones all point at issues. Agents that open or refine tickets follow a repo workflow: issue first, then code, then the ticket stays the source of truth.

The company brain can read Linear. Ops can ask what is in flight without opening the board.

Observability: Better Stack, Sentry, PostHog

Better Stack watches the money path. Uptime monitors hit health endpoints. Heartbeats cover the crons that must not go quiet — SDAT fetch (hourly), SDAT import (every fifteen minutes), SolarLog sync, nightly housekeeping. Those monitors and heartbeats are defined in CDKTF and applied from CI, not clicked together in a vendor UI.

Sentry catches application errors on the web app.

PostHog (EU) is both analytics and rollout. The same project records join_community.* funnel events — steps, field errors, abandonments — and gates operator features and landing-page experiments. Flag keys live in infrastructure config and must match application constants; CI fails on drift.

Metrics: Steep

Business metrics are not ad-hoc SQL. Steep definitions live as YAML in the product repo — communities, contracts, contract intents, meters, meter readings, sites, accounts — and sync to a downstream metrics repo that Steep reads.

When a migration changes meaning, the YAML changes in the same PR. The company brain queries those governed metrics instead of inventing a new count every time someone asks how many communities are active.

How it ships

No Kafka cluster. Fifteen Vercel crons do the work: SDAT fetch and import, SolarLog, virtual meters, the email outbox, Bexio invoice status, scheduled contract changes, plus housekeeping.

GitHub Actions on self-hosted runners: format, typecheck, lint, tests, migration checks, and ratchets — new direct database writes fail CI; PostHog flag drift fails CI.

An internal Slack agent (company brain) has read-only connectors to Linear, PostHog, Steep, and Slite, plus a schema digest and a codebase index. Ops can ask whether a meter is receiving data or how signup traffic looked this month. The architecture is still Postgres, the domain packages, and the crons. The agent is how a small team answers questions without interrupting an engineer.

Where to go next

Building Upgrid is the founder and go-to-market piece. Shipping at Upgrid is how often this stack reaches production. Engineering at N26 is the scale contrast. Energy Tech is the domain hub; the work entry is the short version.

Questions

What tech stack does Upgrid use?

Seven apps and ~70 workspace packages in a Turborepo. Next.js 16, React 19, TypeScript, on Vercel. One Supabase Postgres with row-level security. Linear for issues, Steep for business metrics, PostHog for analytics and flags, Better Stack and Sentry for uptime and errors. Fifteen Vercel crons. Infrastructure config in CDKTF.

Should a small team use a monorepo or microservices?

If the domain is tightly coupled and the team is small, one repo and one schema is cheaper than services. Upgrid is that case. N26’s ~230 services made sense when team boundaries matched service boundaries.

What does it take to bill a Swiss energy community?

Ingest SDAT from the grid operator, allocate consumption on fifteen-minute intervals, price it, emit a Swiss QR-bill PDF, sync to Bexio. ZEV, vZEV, and LEG each change the rules on those steps.

What is a ZEV, vZEV, or LEG?

Three Swiss community models. ZEV (Zusammenschluss zum Eigenverbrauch): self-consumption behind one grid connection. vZEV: the same idea without a shared physical connection. LEG (Lokale Energiegemeinschaft): the local energy community the 2025 reform opened. Different metering, allocation, and billing in each.

RR
Rafael Roman
CTO & Co-founder at Upgrid · Previously N26, Personio, GFT

More writing