ZABDIEL//BLOG
Engineering Notes & Deep Dives
#Architecture#Next.js#SaaS#TypeScript#Devlog

Building ResultApp: Architecting a Multi-Tenant School SaaS for Nigeria

A deep dive into designing an isolated multi-tenant school result compilation platform, custom subdomain routing, and a frictionless credits-and-slots billing model.

November 5, 2026•3 min read•142 views•By Zabdiel Fwesh
Building ResultApp: Architecting a Multi-Tenant School SaaS for Nigeria

The Problem: The Flaws of Subscriptions in Education SaaS

When designing school management software for Nigerian institutions, traditional \$30/month SaaS subscriptions fail for a simple reason: school operations run on distinct academic terms separated by extended holiday periods. Paying monthly for software during 3-month summer breaks creates churn and resentment.

For ResultApp, we threw out subscriptions entirely and designed a Credits & Slots Model:

  1. Slots: A school registers a finite number of active student slots for a term.
  2. Credits: Schools purchase unit credits that never expire. Generating or publishing a term result card consumes credits per student slot.
  3. If school is out of session, cost is zero.

Multi-Tenant Subdomain Routing with Next.js

Each registered school receives a custom subdomain (e.g. meritacademy.resultapp.org). We solved this with Next.js Edge Middleware:

typescript
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(req: NextRequest) {
  const url = req.nextUrl;
  const hostname = req.headers.get("host") || "";
  
  // Extract custom school subdomain
  const currentHost = hostname.replace(`.${process.env.NEXT_PUBLIC_ROOT_DOMAIN}`, "");
  
  if (currentHost && currentHost !== "www" && currentHost !== "app") {
    // Rewrite path to multi-tenant school layout
    return NextResponse.rewrite(new URL(`/schools/${currentHost}${url.pathname}`, req.url));
  }
  
  return NextResponse.next();
}

This rewrite happens at the CDN edge before server rendering, providing zero-latency subdomain resolution.


Database Tenant Isolation & Row Level Security

Data leakage between competing schools is fatal. While schema-per-tenant works for enterprise, maintaining hundreds of distinct Postgres schemas introduces migration nightmares.

Instead, we adopted a unified relational database with Postgres Row Level Security (RLS):

sql
ALTER TABLE exam_scores ENABLE ROW LEVEL SECURITY;

CREATE POLICY tenant_isolation_policy ON exam_scores
  FOR ALL
  USING (school_id = current_setting('app.current_school_id')::uuid);

Every database transaction sets app.current_school_id before querying. Even if an engineer writes a query without a WHERE school_id = ... clause, Postgres guarantees that no data from another school is accessible.


Automated Tamper-Proof Report Card Generation

Nigerian parents demand high-resolution printable report cards with verifiable QR codes and school crest watermarks.

Instead of running heavy Puppeteer instances that exhaust server memory, we implemented high-speed vector SVG compositing on the client and serverless PDF rasterization with @react-pdf/renderer.

  • Result compilation: Instant client-side grade weighting and rank calculation.
  • Verification: Each generated card includes a cryptographically signed HMAC token in the QR code, allowing instant verification by visiting resultapp.org/verify?token=....

Conclusion & Lessons Learned

  1. Design billing for real customer cash flows, not Silicon Valley defaults.
  2. Edge middleware eliminates complex proxy configurations for multi-tenant subdomains.
  3. Database RLS provides ironclad tenant boundaries with unified query performance.

Explore the live platform at resultapp.org.

Was this article valuable?

Leave a reaction below to let me know what you think.

ZF

Zabdiel Fwesh

zabdiel.tech →

Full-stack software engineer & systems architect building resilient SaaS platforms, multi-tenant databases, and modern developer tooling.

Stay in touch.

Unsubscribe anytime.