RLRoman Levchenko
WorkHow it's builtAI workflow
Get in touch

RL-HB / This site

How this site is built

A full-stack monorepo, not a template: a typed contract from database to button, infrastructure as code, and guardrails that make AI agents safe to work with.

01 / This site

Architecture

ClientPublicPrivate networkData and servicesBrowserwebNext.js, server-renderedapiNestJS, Better AuthPostgresDrizzle migrationsClaude APIchat answers, phase 5Voyage AIembeddings, phase 5Resendlead emails, phase 5
  1. Browser
  2. web — Next.js, server-rendered
  3. api — NestJS, Better Auth
  4. Postgres — Drizzle migrations
  5. Claude API — chat answers, phase 5
  6. Voyage AI — embeddings, phase 5
  7. Resend — lead emails, phase 5

Only web is public. The API sits on Railway's private network, and migrations run before each deploy.

02 / This site

Typed end to end

  1. 01

    Zod contracts

    Every endpoint declares its request and response as Zod schemas; the API validates and strips to them.

  2. 02

    Generated OpenAPI

    The schemas produce an OpenAPI 3.1 document, committed next to the code.

  3. 03

    Generated client

    The frontend's typed client is generated from that document. A stale contract fails typecheck and CI.

03 / This site

Testing layers

  1. 01

    Unit and component

    Vitest next to the code, including the architecture tests that enforce layer rules.

  2. 02

    Stories with accessibility

    Every kit component has a story, run in light and dark with an axe check.

  3. 03

    API end to end

    The whole API over HTTP against a real Postgres in Testcontainers, migrated like production.

  4. 04

    Browser smoke test

    Playwright drives the built app on a throwaway database, the way it is deployed.

04 / This site

Choices on purpose

  • pgvector in the Postgres I already run

    The upcoming chat's retrieval uses the existing database instead of a separate vector store.

    Trade-off Fewer moving parts; a dedicated store would scale further.

  • Decisions in ADRs

    Every tool and structural choice is recorded with its reasons and the options I turned down.

    Trade-off Writing takes time, and saves it the next time someone asks why.

  • No motion library

    Reveal and draw animations are CSS, scroll-driven where supported and off for reduced motion.

    Trade-off Simpler effects than a library allows.

  • Typed data before a CMS

    Content started as validated TypeScript data, shaped to move into Postgres with an admin later.

    Trade-off Editing needs a deploy until the admin lands.

How I work with AI agents

© 2026 Roman Levchenko · Warsaw, EU

Built with Next.js, NestJS and Postgres on Railway. See how it's built

  • LinkedIn
  • GitHub
  • Upwork