How This Blog Platform Works: A Practical Tour of the Current Architecture
This post explains how the platform stays maintainable by separating schema ownership, public delivery, and reusable presentation across apps/admin, apps/www, and @repo/ui. It also shows how generated Payload types, Markdown-first posts, and TurboRepo workflows keep content, frontend code, and local development aligned.
Overview
This repository is a monorepo for a personal site and technical blog built around a simple idea:
apps/adminis the canonical source of content schema and admin behaviorapps/wwwis the public delivery layerpackages/uiis the reusable presentation layerpackages/typescript-configis the shared contract layer
That separation looks ordinary at first glance, but the interesting part is how the pieces stay aligned while still moving quickly:
- the CMS defines the shape of content
- generated types make that shape available to the frontend
- the public site fetches from Payload directly through a small service layer
- posts are now Markdown-first, with a custom admin field for preview, media browsing, and inline uploads
This post walks through the architecture from four angles:
- workspace-level module dependencies
- how a public request is served
- how content editing works inside the CMS
- how local development is orchestrated
The Monorepo Boundary Is the First Architectural Decision
At the highest level, the repo is split into three applications and several shared packages:
apps/admin: Next.js + Payload CMSapps/www: public Next.js siteapps/storybook: component sandboxpackages/ui: shared UI components, Markdown renderer, design primitivespackages/typescript-config: sharedtsconfigpresets and generated Payload typespackages/tailwind-config: shared styling configurationpackages/eslint-config: shared lint configuration
The important point is that the public site does not reach into the CMS internals directly. It consumes content through the Payload API, while sharing only stable contracts and presentation primitives.
Module Dependency Graph
This layout does three useful things:
- it keeps app-specific behavior inside each app instead of turning
packages/into a dumping ground - it makes visual consistency a shared concern through
@repo/ui - it keeps the data contract explicit through generated Payload types
The Most Important Contract Lives Between apps/admin and apps/www
In this project, the CMS schema is not just an implementation detail. It is the source of truth for the public site.
That relationship looks like this:
This is the part many full-stack projects leave implicit. Here, it is explicit:
apps/admindefines the schema- Payload generates the TypeScript contract
apps/wwwconsumes that contract in services and route components@repo/uistays focused on presentation, not data fetching
That gives the codebase a clean split:
- schema logic lives in the CMS
- network logic lives in
payloadClient - entity-specific logic lives in
services/payload - rendering logic lives in page components and
@repo/ui
Runtime Topology: Public Traffic Does Not Talk to MongoDB Directly
The public site does not connect to MongoDB directly. It talks to Payload over HTTP.
That is an intentional boundary. It keeps the frontend simple and lets Payload stay responsible for:
- access control
- draft/published filtering
- relationship resolution
- media URL generation
- schema-level hooks
Public Request Sequence
The current posts flow is a good example because it exercises the main layers:
apps/wwwroute componentservices/payload/posts.tsutils/payloadClient.tsapps/adminPayload API- MongoDB for documents
- Vercel Blob or Payload file URLs for media
A few details matter here:
apps/wwwuses Server Components first- it does not bounce through an extra
/apiroute insidewww - the service layer adds default cache and revalidation behavior
- the UI package renders the final shape, but it does not own data access
Posts Are Markdown-First, but the CMS Still Owns the Authoring Workflow
One recent architectural change is especially worth calling out:
Posts.contentis no longer Payload rich text- it is now a custom Markdown field
- the field supports write/preview mode
- it can browse existing media
- it can upload new media inline
- it inserts Markdown image syntax directly:

That is a good example of adapting the authoring model to the real writing workflow instead of forcing everything through a generic rich text abstraction.
CMS Editing and Publishing Sequence
There are several architectural benefits to this approach:
- the writing experience is closer to a developer-friendly Markdown workflow
- media still remains a first-class CMS resource
- preview stays local and cheap
- the persisted content format is plain Markdown, not a heavy editor JSON tree
It also introduces a deliberate tradeoff:
- rich structural editing is lighter than Lexical now
- but
postsare easier to author for technical writing, code snippets, and inline media
Media Is Split Across Metadata and Binary Storage
Media is not stored in one place.
- metadata and relationships live in MongoDB through the
mediacollection - binary files are stored through the Vercel Blob storage adapter
- Payload is responsible for connecting those two layers into usable media URLs
That split is worth understanding because it shows up in both runtime and authoring:
- the CMS talks to Payload
- Payload stores files in Blob
- Payload stores document metadata in MongoDB
- the frontend only needs the final URL
This keeps the public site free from storage-specific logic.
Local Development Is Orchestrated, Not Ad Hoc
The repository does not rely on developers manually starting everything in the right order.
Instead:
- root scripts call TurboRepo
- Turbo coordinates dev, build, lint, and type-check tasks
@repo/uican run its own watch processes for styles and TypeScript outputadminandwwwrun as separate Next.js apps
Local Development Call Flow
This is also where one subtle but important DX improvement shows up:
- app-level
tsconfigpaths now resolve@repo/uitopackages/ui/srcduring development - published package exports still point to built output
- that gives better editor feedback and source navigation without changing production package semantics
Why This Architecture Works Well for This Project
This architecture is not trying to be maximally generic. It is trying to be explicit and maintainable.
The strongest decisions are:
CMS-first schema ownership
apps/adminis the single source of truth for content structure.API-first public consumption
apps/wwwconsumes content through Payload instead of reaching into the database.shared UI, app-specific logic
presentation goes into@repo/ui, while fetching and composition stay in app code.generated types as the contract boundary
schema changes become frontend-safe changes instead of tribal knowledge.Markdown-first posts
long-form technical writing is optimized for the actual authoring workflow.
Tradeoffs and What I Would Watch Closely
No architecture is free.
This one buys clarity, but there are still tradeoffs to manage:
two Next.js apps means two deployment units
separation is good, but environment management must stay disciplined.public content depends on Payload availability
apps/wwwis simpler because it talks to Payload over HTTP, but it also means the admin-side API must be reliable and reachable.generated contracts need to stay in sync
after schema changes,pnpm genis not optional.Markdown is lighter than rich text, not more powerful
it is a better fit for technical posts, but not automatically the right fit for every CMS-managed surface.
The Real Shape of the System
If I had to summarize the architecture in one sentence, it would be this:
The CMS owns structure, the public site owns delivery, shared packages own reuse, and generated types keep all three aligned.
That is what makes the system practical:
- editors get a dedicated admin application
- readers get a focused public application
- developers get reusable components and explicit contracts
- content moves through a predictable path from schema to rendered page
For a blog platform, that is usually the right kind of complexity: visible, bounded, and easy to reason about.