ChanKay Blog
DemosPosts
DemosPosts
ChanKay Blog
GitHubBilibili

© 2026 ChanKay Blog

How This Blog Platform Works: A Practical Tour of the Current Architecture

Mar 19, 2026•6 min read

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/admin is the canonical source of content schema and admin behavior
  • apps/www is the public delivery layer
  • packages/ui is the reusable presentation layer
  • packages/typescript-config is 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:

  1. workspace-level module dependencies
  2. how a public request is served
  3. how content editing works inside the CMS
  4. 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 CMS
  • apps/www: public Next.js site
  • apps/storybook: component sandbox
  • packages/ui: shared UI components, Markdown renderer, design primitives
  • packages/typescript-config: shared tsconfig presets and generated Payload types
  • packages/tailwind-config: shared styling configuration
  • packages/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/admin defines the schema
  • Payload generates the TypeScript contract
  • apps/www consumes that contract in services and route components
  • @repo/ui stays 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/www route component
  • services/payload/posts.ts
  • utils/payloadClient.ts
  • apps/admin Payload API
  • MongoDB for documents
  • Vercel Blob or Payload file URLs for media

A few details matter here:

  • apps/www uses Server Components first
  • it does not bounce through an extra /api route inside www
  • 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.content is 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: ![alt](url)

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 posts are 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 media collection
  • 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/ui can run its own watch processes for styles and TypeScript output
  • admin and www run as separate Next.js apps

Local Development Call Flow

This is also where one subtle but important DX improvement shows up:

  • app-level tsconfig paths now resolve @repo/ui to packages/ui/src during 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/admin is the single source of truth for content structure.

  • API-first public consumption
    apps/www consumes 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/www is 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 gen is 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.

On this page

  • Overview
  • The Monorepo Boundary Is the First Architectural Decision
  • Module Dependency Graph
  • The Most Important Contract Lives Between apps/admin and apps/www
  • Runtime Topology: Public Traffic Does Not Talk to MongoDB Directly
  • Public Request Sequence
  • Posts Are Markdown-First, but the CMS Still Owns the Authoring Workflow
  • CMS Editing and Publishing Sequence
  • Media Is Split Across Metadata and Binary Storage
  • Local Development Is Orchestrated, Not Ad Hoc
  • Local Development Call Flow
  • Why This Architecture Works Well for This Project
  • Tradeoffs and What I Would Watch Closely
  • The Real Shape of the System