Published
- 9 min read
Personal Blog Platform Built with Astro and Cloudflare
1 Project Overview
This project is the technical foundation behind my personal blog and portfolio website.
At first glance, it is simply a writing platform: a place to publish essays, technical notes, project records, and long-form reflections. From an engineering perspective, however, it became a compact but complete web system. It includes content modeling, bilingual routing, static rendering, search indexing, interactive client-side behavior, deployment automation, and verification tooling.
The goal was not to build a complex application for its own sake. The real goal was to create a site that satisfies several practical requirements:
- It should be fast and reliable.
- It should be easy to write and maintain content.
- It should support both English and Chinese posts.
- It should provide useful reading interactions, such as a table of contents and search.
- It should be deployable without managing a traditional server.
- It should remain understandable after future changes.
In that sense, the blog is both a publishing tool and a personal engineering project. It reflects how I think about software: structure first, visible polish second, and long-term maintainability throughout.
2 Architecture Summary
The site is built around a static-first architecture.
Astro generates static HTML pages at build time. Most pages do not need client-side JavaScript to render their main content, which keeps the site lightweight and resilient. Dynamic behavior is added only where it has a clear purpose: search, theme switching, table-of-contents tracking, likes, comments, and small interaction details.
The overall architecture can be summarized as:
Markdown / MDX content
|
v
Astro Content Collections
|
v
Static page generation
|
+--> Pagefind search index
+--> RSS and sitemap output
+--> Cloudflare Pages deployment
|
v
Client-side enhancements
|
+--> Search modal
+--> Theme switching
+--> Table of contents highlighting
+--> Like button API
+--> Comments integration
This design keeps the core reading experience independent from JavaScript while still allowing richer behavior when the browser supports it.
3 Technology Stack
The main stack is intentionally modest:
- Astro for static site generation and page composition
- TypeScript for safer client scripts and project tooling
- Tailwind CSS for utility-first styling
- Astro Content Collections for typed content schemas
- Markdown and MDX for authoring
- Pagefind for static full-text search
- Cloudflare Pages for hosting and deployment
- Cloudflare Pages Functions for lightweight serverless API behavior
- Upstash Redis for persistent like counts
- Disqus as an optional comments layer
- ESLint, Prettier, Astro check, and Playwright for verification
The important point is not that the stack is fashionable. The important point is that each tool has a clear role. Astro owns rendering, Content Collections own structured content, Pagefind owns static search, Cloudflare owns deployment, and Playwright verifies the interactions that are most likely to break silently.
4 Content Model
The site uses Astro Content Collections to define different content types:
- English blog posts
- Chinese blog posts
- Portfolio projects
- Plant notes
Each collection has a schema. Blog posts include fields such as title, description, publish date, hero image, category, tags, draft state, and language. Portfolio entries use a simpler structure: title, date, summary, hero image, tags, and optional external links.
This schema-based approach matters because content is not treated as loose files. Each Markdown file becomes part of a typed content system. Invalid dates, missing required fields, or malformed URLs can be caught before deployment.
For a personal site, this may seem like extra structure. In practice, it prevents small content mistakes from becoming production bugs.
5 Bilingual Structure
The blog supports English and Chinese content as separate collections:
src/content/blog-en
src/content/blog-cn
This separation keeps the model simple. Instead of forcing every post into a multilingual object, each language has its own file set and routing path. English posts live under the default route structure, while Chinese posts live under /cn/.
The advantage is operational clarity:
- English and Chinese posts can evolve independently.
- URLs remain clean and predictable.
- Search indexing can detect both languages.
- Language-specific metadata can be generated at the layout level.
The project also includes language switching logic for related bilingual posts where appropriate. Non-bilingual sections, such as portfolio and plants, are treated as shared site areas rather than duplicated language trees.
This is a practical compromise. It avoids over-engineering multilingual support while still respecting the reality that English and Chinese content have different writing rhythms.
6 Layout and Reading Experience
The site is designed around long-form reading.
The core layout provides:
- a sticky header
- responsive navigation
- dark mode support
- post metadata
- hero images
- reading time
- social sharing
- a table of contents for long posts
- clean prose styling
The table of contents is especially important because some posts are very long. A long article needs more than good typography; it needs navigation support. The TOC tracks headings in the article and highlights the currently visible section while the reader scrolls.
One lesson from this feature is that a reading interface must respect two scroll contexts:
- the article scroll position
- the navigation list position
For short articles, this is almost invisible. For long essays, it becomes essential. The final implementation keeps the TOC behavior tied directly to the rendered article structure, which makes it more reliable across page transitions and long content.
7 Search Design
Search is powered by Pagefind.
Pagefind is a good fit for this site because it generates a static search index during the build process. No hosted search service is required, and no database is needed for search queries. The browser loads the Pagefind bundle and index files only when search is needed.
The search UI is implemented as a custom component that opens a dialog and mounts Pagefind inside it. The search component has to handle several practical details:
- desktop and mobile header instances
- lazy initialization
- focus management
- modal open and close behavior
- responsive layout
- bilingual search
One subtle issue came from rendering separate desktop and mobile search buttons. If the Pagefind mount point uses a global ID selector, the browser may mount the search UI into the first matching element instead of the visible one. The fix was to scope the mount point to the current component instance.
Another issue was bilingual search. Pagefind detects languages and builds separate indexes, but by default it searches based on the current page language. That means a Chinese query from an English page may return nothing unless the other language index is merged. The final search implementation merges the alternate language index so that Chinese content can be found from English pages and vice versa.
This is a good example of a small user-facing bug revealing an architectural assumption: “current page language” is not always the same as “desired search language.”
8 Client-Side Interaction Boundaries
One of the most useful refinements in the project was defining clearer ownership boundaries for client-side scripts.
The current principle is:
- Component-specific interaction stays inside the component.
- Global behavior belongs in
src/scripts. - Page-level or layout-level behavior belongs near the layout or page that owns it.
This matters because Astro pages are mostly static, but client-side behavior still has to survive page transitions, hydration timing, and DOM replacement. If a script is too far away from the DOM it controls, bugs become harder to reason about.
For example:
- The search modal is strongly bound to the Search component, so the logic remains in that component.
- Global UI behavior is grouped separately.
- Article-specific behavior, such as table-of-contents tracking, stays close to the article layout.
This reduces accidental coupling. It also makes future changes easier because the location of a script reflects the scope of the behavior.
9 Likes and Comments
The blog includes a lightweight like system.
The static site itself does not need a backend, but likes require persistent state. This is handled through Cloudflare Pages Functions and Upstash Redis:
Browser
|
v
Cloudflare Pages Function
|
v
Upstash Redis
The browser calls an API route to read or update a post’s like count. Redis stores the count using a stable post key. This keeps the core site static while allowing one small dynamic feature.
Comments are handled through Disqus and can be enabled or disabled through configuration. This keeps comments separate from the core publishing system. If comments are not needed, the site can still function normally as a static blog.
The design philosophy is simple: dynamic features should be optional layers, not structural dependencies.
10 Deployment Model
The site is deployed through Cloudflare Pages.
The production build process performs several steps:
npm run build
|
v
Astro static output
|
v
Pagefind postbuild indexing
|
v
Cloudflare Pages deployment
Astro outputs static files into dist. Pagefind then scans the generated HTML and creates the search index. Cloudflare Pages serves the static output and provides Pages Functions for the like API.
This deployment model has several advantages:
- no traditional server maintenance
- fast global delivery
- simple rollback model through Git history
- predictable build artifacts
- low operational cost
For a personal site, this is close to ideal. The infrastructure stays quiet unless a feature actually needs dynamic behavior.
11 Verification and Quality Control
The project now has a formal verification command:
npm run verify
This runs:
- formatting checks
- lint checks
- Astro type and content checks
- production build
- Playwright end-to-end tests
The end-to-end tests cover the interactions that are easy to break without noticing:
- table-of-contents active section tracking
- desktop search modal behavior
- mobile search visibility
- Chinese search on Chinese pages
- Chinese search from English pages
This is not about creating a large enterprise test suite. It is about protecting the few behaviors that directly affect the reading experience.
For a blog, the most important quality standard is not abstract code perfection. It is confidence: when I publish a post or adjust the site, I should know that the main reading paths still work.
12 Technical Reflections
Building this blog changed how I think about personal websites.
A personal blog can easily be treated as a decorative project: choose a theme, change some colors, publish content. But once the writing grows, the site becomes infrastructure. It has to support memory, organization, search, multilingual expression, and future maintenance.
Several technical principles became clear through this project.
First, static-first architecture is still powerful. Most content websites do not need full server-side complexity. Static rendering gives speed, simplicity, and reliability.
Second, small interactions deserve serious design. A broken search modal or inactive table of contents may seem minor, but these details directly affect whether long-form writing is usable.
Third, bilingual content is not just a translation problem. It affects routing, metadata, search, navigation, and user expectations. The system must allow both languages to coexist without forcing them into the same shape.
Fourth, tooling should serve confidence, not ceremony. Formatting, linting, type checks, builds, and e2e tests are useful because they reduce uncertainty before deployment. They are not valuable because they make the repository look more professional.
Finally, there is a point where optimization should stop. Once the system is stable, understandable, verified, and pleasant to use, further refactoring can become counterproductive. A blog exists to support writing. The engineering should make writing easier, not continuously compete with it for attention.
13 Conclusion
This blog platform is not a large application, but it is a complete web system in miniature.
It combines static generation, typed content, bilingual publishing, search indexing, lightweight serverless behavior, responsive UI, and verification tooling. More importantly, it reflects a practical engineering mindset: build only what the site needs, make the important paths reliable, and stop when the structure is good enough to support real use.
The result is a personal publishing system that I can trust.
It is not just a place where articles are displayed. It is part of how I think, organize, and continue building.