Case study

A property portal for a boutique agency in Thailand

Prototype and full-stack build prepared for Sunway Estates: catalogue, listing pages, enquiry handling, an agent workspace and an administration area — deployed and running on Cloudflare Workers with a serverless database.

55
Listings
4
Agents
6
Enquiries handled
฿2.10B
Portfolio value

The brief

A small agency needs a site that sells

Sunway Estates has been selling premium villas and apartments across Thailand since 2010. Their existing site is an information resource: it presents the portfolio, but it does not let a buyer narrow it down, and it does not give an agent anywhere to work.

The task was a property catalogue that would carry the weight of a sale — a buyer arrives from an advert, needs to filter fifty-five homes down to three, and has to be able to send the shortlist to their partner. An agent then needs to see that enquiry the moment it arrives, together with the property it concerns, without asking a developer.

What follows is the build that resulted, the decisions worth explaining, and the parts a client would care about.

Decisions

Six choices that shaped it

01

State in the URL, not in React

Every catalogue filter is a query parameter. A buyer can send a colleague a link to "villas in Phuket under ฿20M with a pool" and it opens on exactly that view. It also means the back button works, and the client can bookmark a search they will come back to. The filters are the product as much as the property cards are, and a property portal that cannot be shared is a brochure.

02

One server action per form

The enquiry form is used on the home page, the contact page and every listing page. Rather than copy the validation into each page, there is one server action, one zod schema and one place where a lead is stored, rate limited and pushed to Telegram. A change to the enquiry rules is a change in one file.

03

The agent who owns the listing owns the lead

An enquiry is routed to the agent whose listing it came from, and that agent sees it in their cabinet. Agents can only see and edit their own listings. The rules are enforced on the server in every action, not hidden behind a disabled button.

04

Prices as integers

Property prices are stored in baht as whole numbers. A float would introduce rounding error into the one number in the system that must be exactly right. The US dollar figure shown next to it is derived at render time from a static rate, and can be swapped for a live feed.

05

Audit trail from the start

Every administrative action is written to an activity log with the actor, the action and the timestamp. For an agency handling client enquiries it matters to be able to say who changed a price or closed a lead.

06

Degradation over failure

The photograph CDN rate-limits bursts of parallel requests, so a card whose image fails falls back to a local placeholder. The Telegram notification is best-effort: a webhook outage is logged, and the lead is still saved. Neither failure empties the site.

The stack

Boring on purpose

Nothing here is exotic. Every piece is a mainstream choice with a large community, which matters for a client who will have to maintain this after the engagement ends.

Next.js 15

App Router, server components, server actions

React 19

Server components, useActionState for the forms

Cloudflare Workers

OpenNext adapter, no containers

Cloudflare D1

Serverless SQLite, connections by binding

Drizzle ORM

Typed queries, readable schema

Tailwind CSS v4

Design tokens as CSS custom properties

zod

One schema per action, shared with the form

bcrypt + D1 sessions

Revocable, no third-party auth

Playwright

End-to-end checks against the deployed site

Telegram Bot API

Lead notifications

The result

A site an agent can run without a developer

The point of the build is that the day after it goes live, an agent can add a property, correct a price, answer a lead and mark it closed without opening a ticket. The developer is needed for new features, not for the ordinary work of the agency. That was the brief, and it is the thing the client will feel.