Work

Real-estate CMS

A CMS for a property consultancy: listings, pages and a blog, written in a block editor and published with proper SEO. The demo runs as the fictional “Best Real Estate Consultant In Your City™”.

The problem

Small real-estate businesses publish a lot of near-identical content: listings, area guides, blog posts. Off-the-shelf site builders make that slow to write and poor for search, and none of them let you feed content in from your own scripts.

The brief was a CMS the business could run day to day, that ranks well, and that accepts drafts produced elsewhere, including drafts written with AI.

What I built

  • 22 domain models behind the listings, pages and blog.
  • An Editor.js authoring flow, so content is stored as structured blocks and rendered consistently on every public page.
  • SEO meta and JSON-LD schema across the public pages.
  • An import API: blog posts, pages and listings drafted by AI are pushed in through scripts and land as drafts for a human to review.
  • Zero-downtime deploys in production using release directories and an atomic symlink switch.

Key decisions

Inertia instead of a separate SPA and API
The admin is a Vue app, but routing, validation and auth stay in Laravel. One codebase, one deploy, no duplicated form rules, and the import API is the only public JSON surface.
Structured blocks, not HTML
Editor.js stores content as JSON blocks. That keeps rendering predictable, lets the import API validate content before it is saved, and means AI-drafted posts cannot inject arbitrary markup.
Octane on FrankenPHP
Keeping the application booted between requests makes public pages fast without a page cache that has to be invalidated every time an editor saves.
Drafts first for anything imported
Nothing pushed through the API goes live on its own. Scripts can create and update drafts; a person publishes them.

Stack

Laravel 13, Inertia, Vue, Octane/FrankenPHP, Postgres

Results

  • 231 automated tests.
  • 22 domain models, running in production.
  • Deploys switch releases atomically, with no downtime.

Screenshots

Listing editor with Editor.js blocks
Public listing page with structured data

What I would do differently

I would write the import API contract first, as a schema, and generate the validation from it, instead of letting it grow alongside the scripts that call it.

I would add visual regression tests for the public templates earlier. Unit and feature tests catch logic errors, not a broken layout.