WINDOW LAND
Premium glass & aluminium for Dubai's skyline.
Full-stack monorepo for a Dubai-based premium glass and aluminium installation company. Next.js marketing site (25 pages) + admin panel, Node.js Express CMS API, and a Python FastAPI quote calculator — all wired through a Turborepo monorepo with CI/CD.

Numbers about the client's business — useful context, not my engineering output.
Numbers I produced — measurable, attributable to the work I did.
My specific scope on this project — separate from anything the client team supplied.
- Turborepo monorepo scaffolding with shared types
- Next.js 14 frontend — all 25 pages, design system, GSAP + Framer animations
- Admin panel with JWT auth and role-based access
- Express CMS API (Node.js + MongoDB Atlas) for services/projects/blogs
- Python FastAPI quote service with versioned Postgres pricing tables
- LocalBusiness JSON-LD, sitemap, OG images, and GitHub Actions CI
From brief to production system.
The client had no online presence and was losing leads to competitors who showed up in 'glass installation Dubai' searches. They needed a site that visually matched their premium positioning, an admin panel non-technical staff could update without engineers, a quote calculator for inbound leads, and serious UAE-market SEO.
Architected as a Turborepo monorepo with three deployable services: a Next.js 14 frontend (25 pages, animations, admin panel with JWT auth), a Node.js Express API for content management, and a Python FastAPI microservice for quote calculations. GSAP and Framer Motion for the luxe feel. Full LocalBusiness JSON-LD and geo-targeting for UAE search.
Production-ready site at window-land.vercel.app awaiting windowland.ae DNS cutover. Admin team can update services and project galleries without a developer. Quote engine and WhatsApp lead capture wired and tested.
The boundaries that shaped the build.
- 01Premium UAE positioning — design had to feel luxe, not template
- 02Non-technical content team needs to update services + projects without a dev
- 03Quote logic versioned over time so old quotes stay reproducible
- 04Three services in one repo without three separate ops surfaces
What I chose, and what I gave up.
Turborepo monorepo with three services instead of one big Next app
Lower setup complexity. Won independent scaling, language choice per service (Node for CMS, Python for quote math), and a cleaner mental model for the client team.
Vercel for frontend + Railway for backend services
A single hosting bill and one dashboard. Won best-fit hosting per workload — Vercel's edge for the site, Railway's always-on instances for the APIs.
How it shipped, week by week.
Discovery
Calls with the Dubai-based CEO. Mapped services, gathered brand assets, wrote the IA and SEO keyword plan for UAE.
Frontend
Monorepo scaffolding, Next.js App Router, design system, all 25 marketing pages with GSAP animations on the hero and section reveals.
Admin + API
JWT auth flow, role-based admin panel, Express CMS API with MongoDB models, image upload pipeline through Cloudinary.
Quote engine
Python FastAPI microservice for square-meter quote calculations, with Postgres for pricing tables versioned over time.
SEO + ship
LocalBusiness JSON-LD, sitemap, OG images, GitHub Actions CI workflows, Vercel + Railway deploy guide handed to client.
What it does. How it's built.
Features
- 25-page marketing site with cinematic Dubai imagery
- Admin panel with JWT auth and role-based access
- Python-powered quote calculator (m², material, finish)
- Project gallery with category filters
- Service catalog organized by vertical
- Multi-channel contact (WhatsApp, email, phone)
- LocalBusiness JSON-LD for Google rich results
- Cloudinary-backed media pipeline
- GSAP scroll animations + Framer Motion page transitions
Architecture
- 01Turborepo monorepo with shared TypeScript types
- 02apps/web — Next.js 14 App Router + Tailwind
- 03apps/api — Node.js Express + MongoDB Atlas
- 04apps/python — FastAPI quote service + Neon Postgres
- 05Upstash Redis for rate limiting + session cache
- 06Cloudinary for image CDN with automatic transforms
- 07GitHub Actions CI: typecheck, lint, build per app
- 08Vercel for frontend, Railway for backend services
Annotated excerpts.
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from db import get_pricing_for
app = FastAPI()
class QuoteRequest(BaseModel):
service: str # e.g. "curtain_wall"
width_m: float
height_m: float
finish: str # "powder_coated" | "anodised" | "polished"
glass_type: str # "tempered" | "laminated" | "low_e"
@app.post("/quote")
async def quote(req: QuoteRequest):
area = req.width_m * req.height_m
if area <= 0 or area > 200:
raise HTTPException(400, "Area out of range")
pricing = await get_pricing_for(req.service)
if pricing is None:
raise HTTPException(404, "Service not priced")
finish_mult = pricing["finish_multipliers"][req.finish]
glass_mult = pricing["glass_multipliers"][req.glass_type]
base_aed = pricing["base_aed_per_m2"] * area
total_aed = round(base_aed * finish_mult * glass_mult, 2)
return {
"service": req.service,
"area_m2": round(area, 2),
"total_aed": total_aed,
"pricing_version": pricing["version"],
}import { jwtVerify, type JWTPayload } from "jose";
import { cookies } from "next/headers";
const SECRET = new TextEncoder().encode(process.env.JWT_SECRET!);
export type Role = "admin" | "editor" | "viewer";
export interface AdminClaims extends JWTPayload {
sub: string;
role: Role;
}
export async function getAdmin(required?: Role): Promise<AdminClaims> {
const token = (await cookies()).get("admin_token")?.value;
if (!token) throw new Error("Not authenticated");
const { payload } = await jwtVerify<AdminClaims>(token, SECRET);
if (required && rank(payload.role) < rank(required)) {
throw new Error(`Requires ${required}, has ${payload.role}`);
}
return payload;
}
function rank(r: Role) {
return { viewer: 0, editor: 1, admin: 2 }[r];
}Continue browsing
Have a project like this in mind? Let's talk.
Send me a brief and I'll respond within 24 hours.