yufan.me follows a strict five-layer architecture with a one-way import graph that prevents coupling between SSR, client, and shared code. Each layer has a defined set of modules it may import from; violations are caught by the linter and enforced in code review. This separation ensures that server-only code never leaks into client bundles, and that isomorphic utilities stay free of environment-specific dependencies.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/syhily/yufan.me/llms.txt
Use this file to discover all available pages before exploring further.
Five source layers
There are no barrel
index.ts files anywhere in the codebase. Every import targets a specific module path, keeping bundle analysis tractable and preventing accidental cross-layer leakage (bundle-barrel-imports).Import rules
These rules are strict and enforced across the entire codebase:| Layer | May import from | Must NOT import from |
|---|---|---|
routes/* | Any layer; route components must accept plain props | — |
server/* | shared/*, other server/* | client/*, ui/* |
client/* | shared/*, ui/*, client/* | server/*, .server.* files |
ui/* | shared/*, ui/*, client/* | server/*, .server.* files |
shared/* | shared/* only | Everything else |
The server/ sub-tree
The server/ directory is itself organized into four layers with a strict one-way import order (infra → domains → http, domains → render → http):
infra/
Pure primitives with zero business knowledge. Contains the Drizzle pool, Redis storage (unstorage + ioredis), generic HTTP vocabulary (etag, headers, status, errors with DomainError / ActionFailure), email sender, search client, env.ts, logger.ts, rate-limit.ts, and the slug derivation pipeline. infra/ imports nothing from domains/, http/, or render/.
domains/
One folder per business domain — 15 in total: analytics, auth, backup, comments, content, friends, images, music, pages, posts, pt, settings, taxonomies, users, and audit. Each domain has a locked file vocabulary:
| File | Responsibility |
|---|---|
schema.ts | Drizzle table definitions |
repo.ts | Raw database queries |
service.ts | Business logic |
projection.ts | Data transformation for API responses |
cache.ts | Redis caching strategy |
shared/, infra/, and other domains/. Business logic stays inside service.ts; controllers and loaders orchestrate only.
http/
The HTTP perimeter. Contains the Hono entry (app.ts), oRPC procedure base (orpc-base.ts), composed router (api-router.ts), error hook (errors.ts), and OpenAPI export (openapi.ts). Sub-directories:
middlewares/— session, CSRF, install-gate, rate-limit, trailing-slash, visitor-cookie, wp-decoy, RBACcontrollers/— per-domain<name>.controller.ts; admin controllers undercontrollers/admin/resources/— native Hono routes for non-JSON output: feed, sitemap, images, redirects, analytics eventsloaders/— React Router data orchestrators: detail, listing, search, comments, sidebar, pagination, revalidate
render/
SSR output products that never persist. Produces strings, Buffers, or Responses; caching is the caller’s responsibility. Includes: seo/ (meta tags), feed/ (RSS/Atom with PortableText feed renderer), og/ (Open Graph images), calendar/ (SVG generation), avatar/ (Gravatar/QQ fetcher with Redis cache), react-prerender.ts, and image post-processing helpers.
The PortableText pipeline
Posts and pages are stored as PortableText JSON arrays in Postgres JSONB columns. The schema is defined in@/shared/pt/schema with Zod validation, making it the single wire format for both authoring and rendering.
The Tiptap editor in the admin console converts ProseMirror state to and from PortableText via @/shared/pt/bridge — a single file that maps standard blocks to Tiptap built-ins, and custom blocks (image, code, mathBlock, mermaid, musicPlayer, solution, footnoteDefinition, table) to a generic blockCard node. Round-trip fidelity is contract-tested.
On the server, @portabletext/react renders PortableText to HTML during SSR. Custom block components live in @/ui/pt/blocks/ and handle images, syntax-highlighted code (Shiki), math (KaTeX), Mermaid diagrams, and the music player.
Tech stack
| Layer | Choice |
|---|---|
| App router | React Router 7 framework mode, SSR (react-router.config.ts) |
| HTTP host | Hono via react-router-hono-server — perimeter middlewares, resource routers, oRPC mount |
| API | oRPC (@orpc/server + @orpc/client) at /rpc/*, Zod input/output, OpenAPI export in dev |
| UI | React 19, TSX only, shadcn/ui (Base UI variant) under src/ui/components/ |
| Styling | Tailwind CSS v4 (src/assets/styles/tailwind.css), one token cascade for public + admin |
| Editor | Tiptap (ProseMirror) ↔ PortableText bridge; SSR via @portabletext/react |
| Data | Postgres (Drizzle), Redis (sessions, rate limits, generated-image caches) |
| Assets | S3-compatible bucket, opt-in per blog |
| Build | Vite+ (vp) — Vite, Rolldown, Vitest, Oxlint, Oxfmt (viteplus.dev) |