← Back to home
BEHIND THIS SITE

System Design.

How tzheng.dev evolved from a custom cyber portfolio into a versioned, content-driven static engineering system.

This is not a list of technologies.

It is the story of how the site was designed, how its architecture changed, how content moves from source files to production, what broke during the migration, and why some forms of complexity were deliberately left out.

Astro 5 TypeScript MDX Git CI/CD Static Delivery

01 — From Interface to System

The interface could grow. The content model could not.

tzheng.dev began as a highly customized personal homepage.

The original architecture was built around visual identity: custom HTML, CSS, JavaScript, motion, terminal-inspired interactions, project presentations, and a deliberately cinematic first impression.

That model worked well while the website was primarily a portfolio. Then the site began to grow.

Projects needed independent records. Articles needed stable routes, metadata, categories, and long-form layouts.

Search engines needed canonical URLs, structured identity signals, sitemap output, and pages that could exist independently from the homepage.

The problem was no longer visual. The interface could continue growing, but the content model could not. Every new long-term feature increased the risk of turning one presentation layer into the source of truth for the entire site.

Keep the expressive interface. Rebuild the system underneath it.

The homepage remained the identity layer.

Articles and projects became structured content.

Astro became responsible for routing, content generation, metadata, and production builds.

Git became the history of the system.

The objective was not to replace the original site with a framework. It was to separate the parts that needed to evolve independently. The result is a hybrid system: an expressive shell on top of a structured core.

BEFORE
  • Custom homepage
  • Hardcoded content
  • Direct page edits
  • Presentation owns structure
  • Source and published output are nearly the same thing
AFTER
  • Identity layer
  • Structured content
  • Generated routes
  • Validated content model
  • Explicit build system
  • Deployable artifact

02 — The Architecture

Static by design, not by limitation.

Most content on tzheng.dev is public and read-heavy.

Visitors do not need accounts to read a project. An article does not need a database query on every request. A portfolio does not need an authenticated application runtime merely because such a stack is available.

That made static generation the natural default. Astro owns the structural layer of the site.

Astro owns the structural layer:

routing content collections shared layouts metadata RSS generation sitemap generation production builds

MDX stores long-form articles and project records. TypeScript supports the configuration and content tooling around the build. The original homepage keeps the custom frontend behavior that gives it its identity.

This boundary is deliberate.

The framework does not need to own every interaction in order to own the architecture.

The system can be understood as a sequence of transformations:

AUTHORING

Astro pages · MDX · HTML/CSS/JS

SOURCE OF TRUTH

Git repository · GitHub

CI

GitHub Actions · Node 24 · npm ci

BUILD

Astro · npm run build

ARTIFACT

dist/

HOSTING

GitHub Pages

EDGE

Cloudflare

VISITOR

Browser

This is not a zero-JavaScript website. JavaScript remains where interaction, navigation, motion, and interface behavior justify it. The distinction is more important:

JavaScript is a deliberate layer, not the default answer to every page.

The public system remains mostly static, while the expressive parts of the frontend are allowed to remain expressive.

03 — Content as a System

A new article should be a content file, not homepage surgery.

One of the most important architectural changes was separating content from presentation.

Articles and projects are no longer treated as permanent fragments inside a large homepage. They exist as independent Markdown and MDX documents inside structured collections.

DIRECTORY STRUCTURE

src/content/
├── config.ts
├── articles/   (8 files)
└── projects/   (4 files)

Each content type follows a strict Zod schema defined in config.ts.

ARTICLE SCHEMA

title: z.string()
slug: z.string().optional()
date: z.date()
updated: z.date().optional()
excerpt: z.string()
tags: z.array(z.string()).default([])
category: z.string()
featured: z.boolean().default(false)
status: z.enum(["draft", "published", "archived"]).default("published")
readingTime: z.string()

PROJECT SCHEMA

title: z.string()
slug: z.string().optional()
date: z.date()
status: z.enum(["active", "archived", "learning", "open-source", "security"])
type: z.string()
featured: z.boolean().default(false)
priority: z.number().default(99)
stack: z.array(z.string()).default([])
github: z.string().url().optional()
demo: z.string().url().optional()
excerpt: z.string()
View content schema ↗

The content model creates a boundary between what exists and how it is presented. The entire transformation pipeline ensures content is validated before it ever reaches a layout:

MDX FILE
SCHEMA VALIDATION
CONTENT COLLECTION
GENERATED ROUTE
SHARED LAYOUT
STATIC PAGE

This makes content changes predictable. A malformed content record can be detected before publication instead of silently becoming part of a page. A new article does not require editing the homepage. A project can change its metadata without rewriting its layout.

CONTENT decides what exists. LAYOUT decides how it is presented. BUILD decides how it becomes a page.

04 — From Commit to Production

The repository is the source. dist/ is the product.

Every meaningful change begins in Git. Code, content, configuration, metadata, and interface changes enter the same version-controlled history. That makes changes reviewable, traceable, and reversible.

But moving from directly publishable files to Astro introduced an important new boundary. The repository itself is no longer the finished website. It is source code. The production website has to be built.

PUSH TO MAIN
GITHUB ACTIONS
CHECKOUT (actions/checkout@v4)
NODE 24 (actions/setup-node@v4)
npm ci
npm run build
dist/
UPLOAD PAGES ARTIFACT
DEPLOY TO GITHUB PAGES
CLOUDFLARE EDGE
VISITOR
View deployment workflow ↗

A commit can succeed while the build fails. A build can succeed while the wrong artifact is deployed. A deployment can succeed while a route or client interaction is still broken.

Treating source, build, artifact, and deployment as separate failure boundaries makes issues easier to locate. The pipeline does not become more professional by having more stages. It becomes more useful when each stage has a clear purpose.

05 — The Interface Layer

Preserve identity without letting the interface own the architecture.

The website has two different jobs. The first is to create identity. The second is to carry information. Those jobs do not need the same visual intensity.

The homepage is intentionally expressive. It uses a dark visual system, terminal-inspired details, controlled motion, custom interactions, and a stronger sense of atmosphere. It is designed to be remembered.

Articles, project case studies, and system pages have a different responsibility. They need to remain readable for longer periods. They need predictable spacing. They need stable typography. They need layouts that survive different amounts of content.

IDENTITY LAYER

cinematic · interactive · motion-aware · brand-heavy · terminal-inspired

CONTENT LAYER

structured · readable · predictable · content-driven · schema-validated

It is not one interface repeated everywhere. It is one design language with different levels of expression.

Shared interface patterns create continuity between those layers: navigation, metadata, chips, typography hierarchy, spacing rules, cards, footer signals, and the interaction language.

The design system is deliberately lightweight. The project does not introduce an external UI framework simply to prove that one could be used. Custom CSS remains appropriate where the visual system is specific to the site. Astro provides structure where structure is needed.

RESPONSIVE TRANSFORMATION

The page architecture follows another important rule:

Desktop and mobile are not scaled versions of the same composition.
DESKTOP
  • Persistent Story Rail
  • Horizontal Architecture Flow
  • Wide Comparison Layouts
  • Hover-aware Enhancements
  • Persistent Context
MOBILE
  • Sticky Chapter Bar
  • Vertical System Flow
  • Stacked Comparisons
  • Touch-first Interaction
  • Prioritized Reading Order

Desktop layouts can use horizontal relationships, persistent navigation, architecture maps, and wider comparisons. Mobile layouts need a different reading order. Complex horizontal diagrams become vertical flows. Persistent side navigation becomes a compact chapter bar.

Hover states cannot carry essential information. Touch targets, readable measure, spacing, and content priority become more important than preserving the exact desktop composition. Responsive design is treated as a transformation problem, not a shrinking problem.

Motion follows the same logic. Animation can explain hierarchy, state, or movement through the system. It should not delay access to information. Where the operating system requests reduced motion, the interface respects that preference by disabling particle canvases, cursor tracking, and scroll-reveals.

06 — What Broke Along the Way

Real migration logs, not hypothetical.

The difficult part was not choosing a framework. It was deciding what to preserve, what to rebuild, and where the boundaries actually belonged.

Challenge 01 — Migrating Without Flattening the Interface

PROBLEM
The content model needed to change without destroying the original visual identity.
DECISION
Preserve the stable visual layer and rebuild the architecture underneath it.
RESULT
The site gained structured content and generated routes without becoming a generic framework rewrite.

Challenge 02 — Changing the Deployment Boundary

PROBLEM
After the Astro migration, source files were no longer equivalent to the finished website.
DECISION
Make dist/ the explicit deployable artifact. Never deploy source directly.
RESULT
Source, build, artifact, deployment, and delivery became separate failure boundaries.

Challenge 03 — Build Systems Expose Parser Boundaries

PROBLEM
Source that looked harmless as page copy broke the Astro production build when unescaped syntax crossed parser boundaries.
DECISION
Treat npm run build as part of development, not as the final publishing button.
RESULT
Production parser failures became pre-deployment feedback instead of deployment surprises.

Challenge 04 — Mobile Is Not Desktop at a Smaller Size

PROBLEM
Desktop interactions did not always translate correctly to touch devices and narrower layouts.
DECISION
Change interaction behavior where necessary instead of protecting implementation uniformity.
RESULT
Navigation, contact behavior, layout, and interactions became device-aware.

MIGRATION LOG

2026-07-03 9c1d3a2
Migrate site to Astro content system
Moved from static HTML to Astro content collections, Zod schemas, and generated routes.
2026-07-03 b806590
Add GitHub Pages deployment workflow
Established automated CI/CD pipeline with GitHub Actions.
2026-07-03 829e7b9
Restore premium homepage with content-system routes
Preserved the original cinematic homepage through the new Astro architecture.
2026-07-03 4eb4eff
Serve original premium homepage through Astro
Homepage served via Fragment set:html to maintain all original interactions.
2026-07-06 dd321af
fix: resolve astro build syntax errors
Build parser caught unescaped backticks that silently existed in source.
2026-07-06 440d761
feat: improve mobile button layout
Changed email button from mailto to clipboard copy to prevent browser freeze.
2026-07-06 caa226d
feat(system): overhaul architecture page
Ported cursor glow, particle canvas, 3D tilt, and reveal effects from homepage.

07 — Security Through Less Surface

Security begins by removing infrastructure the system does not need.

Static architecture does not mean zero attack surface.

The system still depends on account security, repository permissions, the dependency supply chain, CI/CD permissions, build integrity, hosting configuration, DNS, edge infrastructure, and client-side code.

The security decision is narrower and more honest: Do not create a public runtime surface without a reason.

The goal is not to claim that risk disappears. The goal is to avoid operating public systems that the site does not need.

Static-first is a reduction strategy, not a security guarantee.

TRUST SURFACE

IDENTITY & ACCESS

  • GitHub Account
  • Repository Permissions
  • Branch / Deployment Permissions

SUPPLY CHAIN

  • package.json
  • package-lock.json
  • npm Dependencies
  • GitHub Actions
  • Third-party Actions

BUILD & DEPLOYMENT

  • GitHub Actions Workflow
  • Scoped Permissions
  • Build Artifact
  • GitHub Pages

DELIVERY

  • DNS Configuration
  • Cloudflare Edge
  • TLS / HTTPS

CLIENT

  • Browser JavaScript
  • DOM Interactions
  • External Links

CI/CD BOUNDARIES

The deployment workflow requests only the permissions required for its real job. It explicitly scopes the permissions required for GitHub Pages deployment instead of relying on unrestricted repository write access:

SOURCE (Repository)
BUILD IDENTITY (GitHub Actions)
SCOPED PERMISSIONS
  • contents: read
  • pages: write
  • id-token: write
ARTIFACT (dist/)
DEPLOYMENT (GitHub Pages)
View deployment workflow ↗

WHAT THE SYSTEM INTENTIONALLY AVOIDS

  • No runtime content database
  • No public admin login
  • No server-side user sessions
  • No public user account system
  • No comment backend
  • No intentional advertising trackers

These absences are not slogans. They are architectural decisions. Every missing runtime component is one less public system that needs credentials, patches, authentication logic, session handling, operational monitoring, and incident response.

RESIDUAL RISK

WHAT STATIC ARCHITECTURE DOES NOT SOLVE

  • Account takeover
  • Repository compromise
  • Malicious dependency updates
  • CI/CD misconfiguration
  • DNS compromise
  • Edge configuration mistakes
  • Client-side vulnerabilities
  • Third-party resource risk

A mature security posture should describe its residual risk rather than hide it. A compromised source repository, a malicious dependency, an unsafe workflow change, a DNS mistake, or vulnerable client-side code remain meaningful risks. Security is not one feature. It is the collection of trust boundaries the system chooses to keep.

TRADE-OFFS

WHAT THE SYSTEM GAINS

  • Static delivery
  • Version-controlled content
  • Build-time validation
  • Reproducible deployment
  • Portable MDX files
  • Simple rollback
  • Smaller public runtime surface

WHAT THE SYSTEM GIVES UP

  • No live database editor
  • No public user system
  • No server-side comments
  • A build step for every release
  • More Git-oriented publishing
  • Less runtime flexibility

Every simplification has a cost. The absence of a backend reduces the number of public systems that need to be operated. It also means publishing remains more closely tied to Git and the build pipeline. That trade-off is intentional today.

The hybrid homepage creates another trade-off. Keeping custom global interactions preserves the visual identity of the site. It also means that part of the frontend is less componentized than the newer content system. That is accepted.

A cleaner abstraction is not automatically a better engineering decision if creating it destroys something that already works.

THE PRINCIPLE

Complexity has to earn its place.

tzheng.dev is not designed to use the largest possible stack. It is designed so that every major layer has a reason to exist.

Astro provides structure. MDX provides portable content. TypeScript strengthens contracts around the system. Git provides history and rollback. Automation turns source into a reproducible artifact. GitHub Pages serves the generated site. The edge layer supports public delivery. Custom frontend code remains where expression matters.

Add structure where growth requires it.

Preserve what already works.

Make failure boundaries visible.

Add complexity only when the problem justifies it.