Behind the build

HeyLayla

A private, verified matrimonial platform: a public site, a member PWA, and an operator console over one API.

Live · free pilot Visit the live site →

Designed, built, and operated end to end.

  • TypeScript
  • React 19 PWA
  • Astro
  • Hono
  • Postgres · Supabase
  • Fly.io
  • Vercel

HeyLayla looks simple from the outside: a private matrimonial platform built around intentional introductions and an AI companion called Aunty Layla.

The engineering underneath it is less simple.

I built HeyLayla as a real production system, which meant dealing with the things architecture diagrams often leave out: privacy boundaries, authentication, asynchronous work, database migrations, SEO, deployment failures, cost constraints, AI reliability, and the uncomfortable decisions that appear once real users begin touching the system.

This is a short look behind that work.

Architecture at a Glance

                         Internet

             ┌──────────────┼──────────────┐
             │              │              │
       heylayla.app   app.heylayla.app   admin.heylayla.app
             │              │              │
           Astro        React PWA       React SPA
             │              │              │
             └──────────────┼──────────────┘

                     api.heylayla.app

                       Hono / Fly.io

              ┌─────────────┼─────────────┐
              │             │             │
          PostgreSQL      Worker       AI Providers
           Supabase       Fly.io
              │             │
           Auth +        Durable
           Storage       async work

I deliberately kept the infrastructure small. Vercel serves the public, member, and admin frontends. Fly.io runs the API and background worker. Supabase provides PostgreSQL, authentication, and private object storage.

The goal was never to collect infrastructure. It was to introduce another moving part only when the problem justified it.


Case Study 1: One Website Was Trying to Do Two Opposite Jobs

The problem

HeyLayla originally placed the product and its public presence too close together.

That created a fundamental conflict.

Google needed useful, crawlable pages explaining the product, its approach to marriage, privacy, safety, and related guides.

Members needed almost the opposite: a private application containing personal profiles, conversations, discovery state, interests, and account information that search engines should never index.

Trying to make one frontend excellent at both jobs was the wrong abstraction.

The constraint

This was already a working product.

I did not want an SEO project to trigger a framework rewrite of the member application, disturb authentication, or force the backend into another hosting platform.

The existing React application worked well for an interactive product. The public site simply had different needs.

The decision

I separated the surfaces instead of forcing them together.

heylayla.app
    → Astro
    → public, static, indexable content

app.heylayla.app
    → React/Vite PWA
    → private member application

admin.heylayla.app
    → React/Vite
    → private operator console

api.heylayla.app
    → Hono on Fly.io
    → shared application API

Astro became the publishing surface for the public site and editorial guides. The member and admin applications remained React applications.

The backend did not move.

That gave each part of the system one clear responsibility instead of making routing, SEO, privacy, and application behavior negotiate with one another.

The outcome

The public site can now be optimized for search without exposing the private application to search engines.

Editorial content has a clean publishing path from Markdown into static Astro pages and the sitemap.

The member application remains a focused PWA.

The admin console remains operationally separate.

And all three continue to use the same application API rather than creating duplicate business logic.

The interesting part of this change was not Astro versus React.

It was recognizing that these were different products sharing a backend boundary.


Case Study 2: A Green Deployment That Wasn’t Actually Healthy

One of the more useful architecture lessons in HeyLayla came from production rather than a design session.

The problem

Application code reached production expecting a database schema change that production did not yet have.

The application was deployed successfully.

The process was running.

The health endpoint returned 200.

Authenticated requests were failing.

From the deployment system’s point of view, the application looked healthy. From a member’s point of view, it was down.

That is a much more interesting failure than a server simply refusing to start.

What it exposed

Several assumptions had quietly become coupled:

application version

        ├── expects schema version

        ├── depends on valid configuration

        └── reports health independently

A successful TypeScript build proved the code compiled.

A successful container deployment proved the process started.

Neither proved the deployed system could actually serve a member.

The incident also exposed weak error visibility. The database failure had been reduced so aggressively in logging that the most useful diagnostic information disappeared.

The decision

The production invariant became simple:

schema change

prove migration succeeded

deploy dependent application

exercise a real authenticated path

Database migration ordering became part of release correctness rather than an operational afterthought.

Health also had to mean more than “the HTTP process answered.”

Configuration validation, dependency-aware health, useful exception logging, migration preflight, and authenticated smoke testing all became part of the reliability conversation.

The outcome

The failure changed the way I think about deployment safety in this system.

I no longer consider “build passed” or “container is running” sufficient evidence of a healthy release.

The useful question is:

Can the deployed application, against the deployed schema and production dependencies, complete an important user journey?

That is a much harder test.

It is also the one that matters.


Why I Built It This Way

I could have put HeyLayla on Kubernetes. I could have introduced a dedicated vector database, message broker, orchestration framework, several microservices, and a larger cloud footprint.

I deliberately did not.

For an early production product, operational complexity is itself a cost.

My preference is to make the architecture earn its complexity.

PostgreSQL remains the durable center of the system. Background work gets a worker when synchronous HTTP is the wrong boundary. Public content gets a static publishing system because it has fundamentally different requirements from the product. AI providers sit behind application boundaries rather than owning application state. New infrastructure gets introduced when evidence requires it.

There is still engineering debt. The API’s small Fly.io footprint, for example, creates an availability tradeoff that eventually requires stronger health handling and safe horizontal replication.

I would rather show that tradeoff than pretend the architecture is finished.

Production systems rarely are.

That is the part of software engineering I enjoy most: understanding what the system actually needs today, leaving room for what it may need tomorrow, and resisting the temptation to solve tomorrow’s imaginary problem with today’s unnecessary complexity.