Commercial Real-Estate Platform
Native listings, verified lead capture and deal tools for a brokerage
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.
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.
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.
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.