Technical Analysis of This Website
@jonaszhou|July 1, 2026
This article covers the technical structure of this personal blog—from the Next.js frontend, content system, Route Handlers, and external data integration to building and hosting.
Most of this article was generated by AI and reviewed and edited by me.In one sentence:
This is a personal blog built with Next.js App Router: articles are managed as MDX files, pages are rendered by React components, a small amount of dynamic data comes from Notion and unofficial APIs, it's deployed on Vercel, and Cache Components are used for caching the critical data layer.
Frontend
The frontend is primarily built with Next.js 16 + React 19 + TypeScript, utilizing the App Router and file-based routing. Core routes include:
/: Homepage, featuring an introduction, recommended page cards, Micro Logs, and an article list./about: Personal profile./projects: Project index./{year}/{slug}: Formal blog posts, e.g.,/2026/AI-B2BB-CRM./psychology-effects: Psychology effects collection (Notion data source)./micro-logs: Lightweight log stream (Notion data source)./org-chart-web: Organizational chart web editing tool./links/[id]: Smart redirect links with Open Graph metadata./atom: Atom Feed.
The UI layer is mainly composed of Tailwind CSS 3, custom React components, and MDX content components. Interactive or external embed modules like Mermaid, Tweet, Bilibili, and YouTube are loaded on-demand; Mermaid remains a Client Component, while Tweet / Bilibili are rendered after fetching data on the server.
Layouts are divided into three route groups by purpose:
| Route Group | Path Example | Purpose |
|---|---|---|
(post) | /(post)/2026/about-this-site | Blog posts, with TOC and article navigation |
(page) | /(page)/psychology-effects | Topic page / Tool page |
(no-layout) | /(no-layout)/org-chart-web-editor | Full-screen editor without site main layout |
This frontend is not a landing page structure, but more like a long-term reading interface: the homepage serves as the entry point and index, article pages are for in-depth reading, and topic pages are for displaying Notion-synced data or interactive tools.
Content System
The content layer is not a headless CMS, but file-based MDX + JSON indexing.
Current main types:
| Type | Source | Index | Purpose |
|---|---|---|---|
posts | app/(post)/{year}/{slug}/page.mdx | app/index-posts.json | Official blog posts |
featured pages | app/(page)/*/page.mdx | app/index-featured.json | Featured cards on homepage |
static pages | app/about/page.mdx, app/projects/page.mdx | — | Top-level informational pages |
notion pages | Notion database | Hardcoded pageId in code | Micro Logs, Psychological Effects |
Each article is an independent directory containing page.mdx, optionally components.tsx or notion.ts. This means each individual article can load arbitrary modules, have custom layouts, and leverage Next.js automatic code splitting—redundancy in a single article does not slow down the entire site.
The index file maintains metadata (id, date, title, url, order) for articles and featured pages. Pages read from the index via getPosts() and getFeaturedPages(), rather than scanning the filesystem at build time.
New articles can be created using the pnpm new-post script: interactively input a title and description, automatically generate an MDX skeleton, and write the entry to the index (Chinese titles can be converted to English slugs via the Tencent Cloud Translation API).
Markdown Pipeline
The Markdown build pipeline is based on @next/mdx, configured in next.config.js:
pageExtensionsincludesmdandmdx, allowing MDX files to serve directly as pages.experimental.mdxRs: true: Enables the Rust-based MDX compiler for faster compilation.cacheComponents: true: Enables the Next.js 16 Cache Components model.
mdx-components.ts maps MDX elements to custom components, forming the site's style guide:
| Category | Component |
|---|---|
Typography | H1 / H2 / H3, P, A, OL / UL / LI, Blockquote, HR |
Code | Code, Snippet (replaces <pre>) |
Media | Image, Figure, Caption |
Embeds | Tweet, YouTube, Bilibili, Mermaid |
Auxiliary | Callout, DataTable, Footnotes (Ref / FootNotes / FootNote) |
Chinese Fonts | Kai (KaiTi), Song (SongTi), Hei (HeiTi), JHSong (JingHua Old SongTi) |
Heading anchors support the [#custom-id] notation: by adding a custom id at the end of a heading, H1/H2/H3 components will automatically generate a visible # link. The TOC component on the left side of the article page scans the headings within article on the client side and prioritizes reading these custom anchors.
Fonts and Theme
The font strategy balances Western and Chinese typography:
- Western: Inter (body text), Kanit (Logo), Roboto Mono (monospace).
- Chinese: Founder Kai/Song/Hei (
cn-fontsource), Sarasa Gothic Mono SC (subsetted, retaining only commonly used Chinese characters), Jinghua Old Song (subsetted). - Subsetting scripts are located at
scripts/subset-sarasa-font.jsandscripts/subset-jhsong-font.js, with outputs inpublic/fonts/.
Theme switching is executed before the initial screen render via an inline themeEffect script, supporting light / dark / system states, and is persisted with localStorage. Vercel Analytics and Speed Insights are injected into the root layout.
Backend
Server-side capabilities are provided by Next.js Route Handlers, with code in app/api/ and several route.ts files:
| Endpoint | Purpose |
|---|---|
app/api/posts/route.ts | Returns post list JSON |
app/api/featured/route.ts | Returns featured page list |
app/api/posts/view/route.ts | Post view count (framework built, incr pending implementation) |
app/api/pages/view/route.ts | Page view count |
app/api/bilibili/route.ts | Bilibili video metadata |
app/atom/route.ts | Generates Atom Feed |
app/opengraph-image/route.tsx | Homepage OG image |
app/(post)/og/[id]/route.tsx | Single post OG image |
app/about/opengraph-image/route.tsx | About page OG image |
Upstash Redis serves as a cache layer for external APIs: Tweet and Bilibili video information is fetched on the server side and written to Redis, falling back to the cache on request failures.
Unofficial Notion API (app/(page)/psychology-effects/notion.ts) is used to read two databases:
- Micro Logs: Short logs displayed on the homepage and
/micro-logs. - Psychology Effects: Experimental entries for
/psychology-effects.
This data is retrieved via getMicroLogs() and getExps(), and cached using "use cache" + cacheTag.
External Data and Caching
Next.js 16 removed implicit fetch caching, and the project has migrated to the Cache Components model:
export async function getPosts() {
"use cache";
cacheLife("minutes");
cacheTag("posts");
// ...
}
| Data Functions | Source | Cache Strategy |
|---|---|---|
getPosts() | index-posts.json | "use cache" + cacheLife("minutes") + cacheTag("posts") |
getFeaturedPages() | index-featured.json | "use cache" + cacheTag("featured-pages") |
getMicroLogs() | Notion API | "use cache" + cacheTag("micro-logs") |
getExps() | Notion API | "use cache" + cacheTag("psychology-effects") |
Tweet / Bilibili | External API + Redis | Server-side fetch, Redis persistence |
The client no longer uses SWR polling or setInterval to refresh Notion data; updates rely on cache expiration or subsequent webhook + revalidateTag for on-demand invalidation.
proxy.tsx (Next.js 16 replacement for middleware) currently only injects the x-edge-age response header for observing edge TTL.
Build & Run
Package management uses pnpm.
Common commands:
pnpm dev # Development server (binds to 0.0.0.0)
pnpm build # Production build
pnpm start # Production start
pnpm new-post # Interactive post creation
pnpm format # Prettier formatting
pnpm format:check # Format check
Build is done by next build, with Turbopack enabled by default. MDX pages are compiled into React components at build time; OG image routes are dynamically generated on request (ImageResponse + local woff fonts).
Environment variables (.env.example) are mainly for local scripts: Tencent Cloud Translation API key (pnpm new-post) and Upstash Redis Token (Tweet / Bilibili cache).
Hosting & Observability
The site is hosted on Vercel, domain jonaszhou.com.
- Client analytics:
@vercel/analytics - Performance monitoring:
@vercel/speed-insights - Feed:
/atomprovides Atom XML - OG: Each page and article has a dynamically generated Open Graph image
Why This Design
This architecture suits my usage patterns:
- MDX is suitable for long-term writing, and each post can be an independent "mini-app."
- JSON indexing allows the homepage and feed to avoid filesystem scanning, keeping maintenance manageable.
- Notion serves as the editing interface for Micro Logs and experimental data, which is faster to write than modifying code.
- Next.js App Router keeps content, components, APIs, and OG generation in a single project.
- Cache Components allow static shells to coexist with Notion dynamic data without requiring client-side polling.
- Redis caches external API results to prevent Tweet / Bilibili embeds from slowing down pages or triggering rate limits.
It's not the simplest blog architecture—no full CMS using Notion or Contentlayer directly, and no multilingual routing—but it brings "personal profile, formal articles, lightweight logs, interactive tools, external embeds" into one maintainable Next.js project, while preserving room for further evolution with Cache Components and on-demand invalidation.