
Building CbamTrack: Next.js SaaS Architecture for CBAM Compliance
Cbam Track: Building a Next.js SaaS for EU Carbon Border Compliance
When we set out to build Cbam Track, the goal was straightforward: replace spreadsheets and manual calculations with a platform that makes EU CBAM compliance feel like filling out a well-designed form.
The reality? Building a compliance SaaS for a regulation that's still evolving, where every calculation needs to be auditable, and where the users range from logistics managers to sustainability consultants — that's a different story.
Here's how we built it, the tech decisions we made, and what we learned along the way.
The Problem: CBAM Is a Paperwork Nightmare
The EU Carbon Border Adjustment Mechanism requires importers to report embedded emissions for every ton of steel, aluminum, cement, fertilizer, electricity, and hydrogen they bring into the EU. That means:
- Collecting emission data from suppliers across the globe
- Applying the correct calculation methodology (production-route-specific benchmarks, default values, country-specific grid factors)
- Generating quarterly reports in the CBAM Transitional Registry format
- Keeping audit trails for potential verification
Most companies handle this with Excel. The result: errors, missed deadlines, and anxiety every quarter.
Tech Stack Choices
Next.js 16 App Router
We chose Next.js for its hybrid rendering model. Marketing pages are statically generated at build time (fast, SEO-friendly), while the dashboard is fully dynamic with server components handling data fetching.
// Marketing pages — static, fast
export default async function BlogPage() {
// This runs at build time or on-demand revalidation
return <BlogPosts />
}
// Dashboard — fully dynamic with auth
export default async function DashboardPage() {
const { userId } = await auth()
const company = await prisma.company.findFirst({ where: { userId } })
return <DashboardShell company={company} />
}
Tailwind CSS v4 with OKLCH Colors
We switched to Tailwind v4 early in development primarily for the OKLCH color space — more consistent luminance across light and dark modes. The bg-primary/8 syntax for alpha channels made overlay effects trivial.
PostgreSQL + Prisma
Financial compliance data demands relational integrity. Every emission record references a specific product, which belongs to a company, which has a quarterly report. Prisma gives us type-safe queries and migrations that match our deployment workflow.
The Payment Puzzle: Lemon Squeezy
We integrated Lemon Squeezy for subscription billing. Their webhook-first approach fits the Next.js API route pattern well:
export async function POST(req: Request) {
const payload = await validateWebhook(req)
switch (payload.meta.event_name) {
case "subscription_created":
await activateSubscription(payload.data)
break
case "subscription_cancelled":
await deactivateSubscription(payload.data)
break
}
return new Response("OK", { status: 200 })
}
Authentication: Clerk
Clerk handles everything from sign-up flows to multi-factor authentication. We route Clerk webhooks through our middleware layer to sync user data with our PostgreSQL database automatically.
Architecture Decisions
Feature-First Directory Structure
Instead of grouping by technical role (components/, pages/, utils/), we group by feature:
src/
features/
emissions/ # Emission data CRUD
reports/ # CBAM report generation
products/ # Product management
simulator/ # What-if scenario tool
core/
engine/ # CBAM calculation engine
validators/ # Zod schemas
Each feature is self-contained with its own components, server actions, and page files. This makes it easy to reason about what touches what — critical for compliance software where you need to trace every data transformation.
The Calculation Engine as a Pure Module
The CBAM calculation engine lives in src/core/engine/ — a pure TypeScript module with zero framework dependencies. It takes inputs, applies the current regulation rules, and returns results:
// Pure calculation — testable, auditable, replaceable
function calculateEmbeddedEmissions(input: CalculationInput): CalculationResult {
const directEmissions = applyBenchmark(input.sector, input.productionRoute, input.volume)
const indirectEmissions = calculateIndirectEmissions(input)
const total = directEmissions + indirectEmissions
const certificates = calculateCertificateCost(total, etsPrice)
return { directEmissions, indirectEmissions, total, certificates }
}
This separation means we can:
- Test every calculation path (30+ unit tests covering all sectors)
- Swap the engine when EU regulations change (which they will)
- Reuse it across the free calculator, the dashboard, and API
Tenant Isolation at the Database Level
Every database query enforces tenant isolation:
async function getCompanyReports(companyId: string) {
return prisma.report.findMany({
where: { companyId }
})
}
Combined with middleware-level checks, this ensures Company A never sees Company B's data — a non-negotiable requirement for compliance software.
The Hard Parts
Keeping Up with Evolving Regulations
CBAM regulations aren't static. The benchmark values shifted from the transitional period to the definitive regime. New CN codes were added. The free allocation phase-out schedule changed.
Our solution: version the calculation engine. Each regulation version is a separate set of constants and formulas. Old reports stay reproducible with the rules that were in effect when they were generated.
Making Complex Calculations Accessible
CBAM calculations involve production route benchmarks, default value markups, country-specific grid factors, and precursor material emissions. The science is complex, but the UX shouldn't be.
We built the free CBAM calculator as a progressive disclosure form:
- Select your sector
- Enter your volume
- Get an instant estimate
No jargon, no mandatory fields for obscure parameters. The advanced settings are there if you need them, hidden if you don't.
PDF Generation That Looks Professional
CBAM reports need to look like official documents. We used @react-pdf/renderer to generate PDFs that match regulatory formatting requirements — proper headers, tables with aligned columns, signature blocks.
What's Next
The platform is live. The calculation engine passes all tests. We're now focused on:
- ERP integrations — connecting directly to existing inventory and procurement systems
- Supplier data portals — making it easy for suppliers to submit their emission data directly
- Anomaly detection — flagging unusual emission patterns before they become compliance issues
Lessons for Fellow SaaS Builders
- Start with the hardest calculation first. If you're building compliance software, the regulation logic is your moat. Get it right before building the UI around it.
- Invest in tests for regulatory logic. When the EU publishes an update, you need to know exactly what changed and what broke.
- Free tools grow your audience. Our free CBAM calculator generates more qualified leads than any ad campaign could.
- Choose boring, well-tested infrastructure. Next.js, PostgreSQL, Prisma — these aren't exciting choices, but they don't break at 3 AM.
Building Cbam Track has been a journey of translating complex EU regulation into clean TypeScript. If you're navigating CBAM compliance yourself, try the free calculator or reach out — we'd love to hear how you're handling it. For an overview of top compliance tools, see our alternatives hub.
R. Emrah Gökkaya
CbamTrack builds CBAM compliance software for EU importers. We help SMEs automate quarterly emission reporting with live ETS pricing and IR 2025/2621 compliant calculations.
Learn more about CbamTrack →Ready to simplify your CBAM compliance?
Subscribe today and generate your first CBAM report in minutes.