YeagerZhao Blog: A Bilingual Publishing System with Next.js and Notion
YeagerZhao Blog is my personal website for projects, technical writing, and travel photography. I started with a simple wish: a place of my own to keep these things together. Once the pages began to hold long-form articles, the harder problem became maintaining the connection between local drafts, the Notion CMS, and the public website.
Today, Next.js serves the site, Notion manages content, and Alibaba Cloud OSS stores article illustrations and photography display assets. Markdown, source images, and release snapshots stay local. AI helps organize and translate drafts. This overview explains what the system does, how its parts work together, and what remains to be improved.
Visit YeagerZhao Blog. For the longer account of the implementation, problems, and trade-offs, read the architecture article.

This screenshot preserves an earlier design. The live site is the reference for the current interface; the diagrams below explain responsibilities and data flow.
Connecting Writing to Presentation
Different Content Needs Different Reading Experiences
Home, Blog, Product, Journey, and About are the five entry points. Blog develops a topic and its implementation; Product introduces a project's purpose, architecture, and status. Journey gives photographs and videos more space, while About presents my profile and experience.
These sections share navigation and visual rules without sharing every layout. Long articles need a table of contents and comfortable spacing. Project lists need clear summaries. Albums need large images, navigation, and enlargement. Reusing a content structure does not require every page to use the same card.
Content Publishing and Code Deployment
An article starts with Chinese Markdown and images in a folder outside the code repository. After discussion and factual review, I prepare the English version. Publishing tools convert the body to Notion blocks, upload local images to OSS, and insert their URLs. The website reads the page matching the language, content type, and slug.
Text revisions normally require a Notion update and cache revalidation. Changes to layout, fonts, or functionality require a code deployment. Writing materials stay outside Git, and editorial updates can proceed independently of unfinished interface work.

Notion stores content without automatically pushing a new web page. Publishing tools write it, the website reads it, and the browser fetches images. Existing caches must also be invalidated.
Frontend and Server Responsibilities in One Application
Separate Routes, Data, and UI
The project remains a single Next.js App Router application. Reading Notion, generating metadata and HTML, proxying images, and handling revalidation fit within its server-side responsibilities.
src/app Routes, metadata, loading, and HTTP entry points
src/server Notion queries, mapping, page content, cache, and image proxy
src/ui Tokens, shared components, views, typography, and motion
src/i18n Language routing and interface text
src/types Shared content types
SKILLS Local writing, publishing, and operations toolsServer Components call query functions directly. Notion credentials remain server-side, and mappers turn API responses into typed titles, tags, and image references. API handlers and pages reuse this layer rather than making pages call their own JSON endpoints.
From URL to Article Body
For /en/product/yeagerzhao-blog-architecture, the page extracts locale and slug, then queries Content with ContentType=product, Language=en, and Published=true. Metadata supplies the Notion page ID. The body reader fetches paginated blocks and children within its supported depth before the article view renders them.
The current Notion SDK queries data sources. Although the environment variable is named NOTION_CONTENT_DATABASE_ID, its value must identify the expected object and have appropriate integration access. A network failure, authorization error, and missing entry require different handling.
Four Notion Databases
Shared Fields, Independently Maintained Bodies
Content holds Blog and Product records, distinguished by ContentType. Both use title, summary, date, tags, cover, and publication fields. Projects also have an external URL and status. Bodies live in Notion pages.
Chinese and English versions share a slug and have different Language values. Publishing tools check ContentType + Slug + Language for duplicates, replacing the separate TranslationKey convention. Notion itself does not enforce that combination as a unique database constraint.
Blog and Product entries with the same slug remain separate records. Updating the blog does not replace the project page. Their facts and images can be shared, but publication must target each explicitly.
Structures for Journey and About
Each Journey record represents an album, Vlog, edit, or travel note. JourneyPhoto links individual photographs to albums. The list reads covers and metadata; an album page reads its photos. Video entries retain Bilibili or YouTube links.
About uses SectionType to distinguish profile, timeline, and highlight records, filtered by language and publication status. Infrequently changed social links remain in site configuration. These four databases reduce the overhead of the earlier design, which assigned separate databases to many small sections.
Stable Image Delivery
Article Images and Native Notion Files
Local article images are uploaded to OSS, referenced as external Notion images, and fetched directly by browsers. Native Notion uploads use /api/notion-image, which validates the host, response type, and size, and can refresh a source address using a block ID when possible. Signed Notion file URLs expire.
Image caching is separate from page caching. Replacing bytes at the same OSS URL can leave browsers displaying an older file after a page refresh. The publishing tools now include a content hash in object names: changed image bytes produce a new URL, Notion references that URL, and the old address remains available for restoring an earlier draft. The three diagrams reuse the architecture article's white-and-orange palette and cover system responsibilities, image preparation, and update paths.
Photography Preparation
Journey originals and editing projects remain local. Tools correct orientation, resize images, remove EXIF/GPS, and process watermarks before uploading a display master. The site uses thumb, preview, and full presets for different viewing contexts.
Preparation currently uses an approximately eight-megapixel area budget, accommodating panoramic images. Full means the processed public display image, not RAW. Watermarks provide attribution clues and require practical testing. Disabling right-click cannot prevent copying pixels a browser displays.

Shared Rendering and Design Rules
Read the Structure Before Styling It
Tables and nested content cannot be rendered from top-level blocks alone. A Notion table defines structure while table_row children contain cells. The reader and renderer must handle both. The custom renderer covers common text, annotations, lists, code, images, tables, and several media types, with appropriate fallbacks elsewhere.
It supports website reading rather than reproducing the entire Notion editor. Code retains text and language information; equations, synced blocks, and database views still have limitations. Supporting a new type starts with checking complete data and appropriate HTML.
Consistent Typography and Interaction
The frontend is organized into tokens, components, views, and styles. Shared variables define colors, typography, spacing, radii, and motion. Article panels follow homepage panel background rules; similar cards reuse reveal and hover behavior.
Published long-form typography uses a 16px body size, 1.85 line height, and 18px paragraph spacing, with more space around images and code. Summaries appear in listings and metadata. The table of contents is generated from body headings and follows the active chapter.
The local revision displays the full title above the body. The left rail retains a compact two-line title, date, and chapter list, without tags or subsection expansion. Projects with an external link have a sticky button on the right; on phones it stays in the lower-right corner. Article images display directly without original-file links that might trigger a download. Article and project pages also include the YeagerZhao profile and platform links at the bottom.
The Hero fills the first viewport. Reading pages use a fine blue paper texture and slow lighting, while the body panel keeps a stable background and a thin illuminated edge. Reduced-motion settings stop the background animation. These interface changes remain local; publishing this overview does not deploy them to the public site.
Newsreader and Spline Sans Mono resources are self-hosted. General Chinese text uses system fonts; the homepage greeting retains its Source Han Serif style. Self-hosting stabilizes resource delivery, while system glyphs vary by device.
Caching and Publication
Prefetching and Content Caching
Pages and data use a one-hour default revalidation interval, with Notion query and body results stored through unstable_cache. The previous production queries also use an in-process Map. Local code removes that layer and groups invalidation for specified paths by Blog, Product, Journey, and About. A default full-site refresh still invalidates all domains; an open browser may require navigation or a reload.
Primary navigation uses Next Link's default prefetch behavior. Detail links prefetch on hover, focus, or touch intent, and loading boundaries provide feedback during cold navigation. The local revision applies the same intent-based prefetching to Home detail links.
Home no longer warms multiple article bodies through client JSON requests. Prefetching can reduce waiting and skeletons acknowledge a click, but actual retrieval still matters. Production checks distinguish click feedback, readable content, and image display.
Verify Content, Then Revalidate
Notion blocks are written in batches, so replacing a body is not atomic. The tools now distinguish --overwrite, which updates properties only, from --replace-content, which replaces the body. Replacement snapshots properties and the recursive block tree, appends new blocks, and reads them back. Only a matching result allows the old blocks to be retired and properties committed. A failure triggers an attempt to remove incomplete content and restore the previous version. Chinese and English pages are still written sequentially, without a cross-page transaction, so both require verification.
After the Notion write, an authenticated /api/revalidate request invalidates paths and cache tags. The public pages must then be checked for the new body and diagrams. Blog and Product records with the same slug are distinct; this release explicitly updates both language versions of Product rather than assuming the blog release also updated the project.

Production code is deployed through GitHub and Vercel. Content changes go through Notion, while interface changes go through a build. The earlier Docker, Nginx, and ECS design remains an optional self-hosting direction rather than the current production topology.
Project Status and Next Priorities
The live system connects bilingual content, a shared Content database, Journey galleries, About updates, local Markdown publishing, article rendering, and on-demand revalidation. AI assists writing and translation; scripts perform conversion and upload. I review personal judgments, technical claims, and final content.
This round adds publication snapshots with read-back and recovery, database-list pagination, content-hash image URLs, and recursive body JSON exports. Website changes also preserve retrieval errors, improve SEO and actual translation links, and mount only the current and neighboring album photographs. They await local review and deployment. Passing builds and tests validate the corresponding implementation, not its production deployment.
The next priorities are interrupted-publication recovery, Markdown export, offline images, and conflicts between online Notion edits and local drafts. Pull saves page properties and the complete recursive block tree as JSON; it is not yet an offline article bundle. Automatic webhooks still require event validation, deduplication, and failure recovery.
Building this site has turned a place to display content into a maintainable publishing system. Articles can be revised independently, the interface can evolve, and local drafts, CMS records, and public URLs have clear relationships. Future changes will focus on complete publication, readable images, and recoverable versions.