← Back to projects

Commercial Real-Estate Platform

Native listings, verified lead capture and deal tools for a brokerage

ScopeFull platform, front end to content model
TypeCommercial real estate
StackNext.js / React 19 / Sanity / Mapbox
StatusProprietary · Client Project

This is a client project. The write-up is intentionally generalized: no client, brand, or product names, no data, and no source code are shown. What follows is my own account of the constraints, the architecture, and the decisions I made.

REAL-ESTATE PLATFORM STUDIO BROKER EDITS LISTING PAGES EMAIL GATE 6 CALCULATORS PUBLISH → INDEX → VERIFY LEAD → UNDERWRITE
01Overview

A commercial real-estate platform for a brokerage: a public site with native property listings, a clustered map, a media section and team profiles; a gated suite of deal calculators for investors; and a content studio the brokerage’s own team uses to publish.

Every listing is a real page on the brokerage’s own domain, rendered server-side from their content model rather than embedded from a third-party portal. That is the point of the build: the inventory, the leads it generates, and the pages search engines index all belong to the client.

I built it end to end — 17 public routes, the content model, the lead-verification flow, the calculators and the map.

02Publishing Without a Developer

The content model is eight document types: listings, brokers, deals, testimonials, media posts, page copy, site settings, and captured leads. Seven of them are the brokerage’s to edit. Page copy is a document too, so hero text and calls to action change without a deploy.

Pages render statically and revalidate on a sixty-second window, with a webhook from the content system that purges the cache tag the moment an editor publishes. An editor sees their change in seconds instead of waiting out the window, and if the webhook never fires the site still self-heals on the next revalidation. The webhook rejects any request without the shared secret.

The studio is mounted inside the application itself on an optional catch-all route. The editing tool lives at the same domain as the site, so there is one deployment to operate rather than two.

03Key Features
Native Listing Pages
Each property is a real route on the brokerage’s domain, server-rendered from the content model, with images served through the framework’s image pipeline.
Clustered Property Map
Listings become a GeoJSON source on a vector map. Clusters collapse below zoom 13 and grow with density; the detail page reuses the same component with clustering switched off.
Multi-Field Search
Filter by property type, listing type, status, submarket, price range and floor area, plus a free-text match across name, address, submarket, city and description.
Email-Verified Lead Capture
A six-digit code with a ten-minute expiry, bound to the email address that requested it and gated behind a CAPTCHA challenge. Verified leads land in the content system and notify a broker by email.
Six Gated Deal Calculators
A full deal evaluator with PDF export, plus loan payment, cap rate, cash-on-cash return, net operating income and debt-service coverage. All compute in the browser; all sit behind the email gate.
Tested Where It Matters
41 test cases across 11 files, covering code generation and checking, session tokens, every listing filter, HTML escaping and the lead webhook.
04Technical Architecture

Next.js 16 on the App Router with React 19 and TypeScript. Seventeen public routes: fourteen static pages and three dynamic ones for listings, media posts and team profiles. The embedded studio takes an optional catch-all of its own.

Listing data leaves the content system through a single query and is mapped to one canonical listing type at the boundary, so image-URL building and null handling happen in exactly one place. Filtering then runs in memory over that type. For a single brokerage’s inventory this keeps every filter a pure function that unit tests cover directly, with no search index to run or keep in sync.

Lead verification is deliberately server-side. The route validates the address, checks the CAPTCHA token, then issues a signed challenge carrying the code, its expiry and the email it was issued for. Verification re-checks all three, so a code cannot be replayed against a different address. Delivery goes through a transactional email API; in development the code prints to the console, so the whole flow can be exercised without sending mail.

Images use the framework’s image component with remote patterns limited to the content CDN and one stock source, behind a fill-based wrapper that falls back to a gradient placeholder when a listing has no photo. The content client has its own CDN cache turned off on purpose: the framework request cache plus the revalidation webhook is then the only cache layer, which leaves one thing to purge instead of two.

05Tech Stack
Next.js 16 (App Router)Routing, rendering & incremental regeneration
React 19UI
TypeScriptCanonical listing type & calculator inputs
SanityEight document types, embedded studio
GROQListing, media & team queries
Mapbox GLClustered property map
ResendVerification codes & broker lead notifications
Cloudflare TurnstileCAPTCHA on lead capture
Vitest41 test cases across 11 files