System Architecture
Andrew Gao’s personal website is a statically generated site built with Astro that uses Notion as a headless CMS. The architecture follows a pre-build synchronization pattern where content is fetched from Notion during the build process and converted to local MDX files.Tech Stack
Frontend Framework
Astro 5.5.4 - Static site generation with file-based routing
CMS
Notion API - Headless CMS for content management
UI Libraries
- React 19 (Image component)
- Solid.js 1.9 (Interactive components)
Styling
Tailwind CSS 4.0 via Vite plugin with Typography plugin
Core Design Decisions
1. Pre-build Content Synchronization
Decision: Fetch and convert Notion content to local MDX files at build time rather than runtime. Rationale:- Performance: Static generation eliminates API calls during page loads
- Reliability: Site remains functional even if Notion API is down
- Cost: No API rate limit concerns in production
- SEO: Fully static HTML is optimal for search engines
src/lib/notion-download.ts:30
2. Multi-Framework Strategy
The project uses three different frameworks strategically:Astro - Server-side rendering
Astro - Server-side rendering
Used for layouts, static pages, and server-side data fetching. Provides zero JavaScript by default.
React - Image optimization
React - Image optimization
Used specifically for Astro’s
<Image /> component which provides automatic image optimization.Configured in astro.config.mjs:18-20 to only include the Satori component.Solid.js - Interactive components
Solid.js - Interactive components
Used for client-side interactivity (Map, Navigation) with smaller bundle sizes than React.Components use
client:only="solid-js" directive for hydration.3. Incremental Content Updates
The sync process implements smart caching to avoid unnecessary re-downloads:This optimization significantly reduces build times by only syncing modified content.
4. Type-Safe Notion Integration
All Notion API responses are typed using official SDK types:src/lib/notion-cms.ts:23-25).
Project Structure
Build Process
The build follows this sequence:Pre-build Hook
jiti scripts/index.ts runs before astro dev or astro buildConfigured in package.json as predev and prebuild scripts.Content Sync
- Downloads blog posts from
NOTION_BLOG_DB_ID - Downloads projects from
NOTION_PROJECTS_DB_ID - Converts Notion blocks to Markdown
- Downloads and optimizes assets
- Writes MDX files to
src/content/
Astro Build
- Processes MDX content collections
- Generates static pages
- Optimizes images with Sharp
- Creates sitemap
Environment Variables
The system requires these Notion credentials:Notion API integration token from https://www.notion.so/my-integrations
Database ID for blog posts
Database ID for project entries
Database for dynamic navigation items
Database for places/map visualization
Databases for yearly knowledge entries (2024, 2025, 2026)
Performance Characteristics
Build Time
- Initial: ~30-60s (full sync)
- Incremental: ~10-20s (changed content only)
Page Load
- Static HTML: Less than 50ms TTFB
- Zero runtime API calls
- Optimized images (WebP)
Bundle Size
- Base: ~50KB (Astro + Tailwind)
- Interactive pages: +30KB (Solid.js)
Next Steps
Notion CMS Integration
Learn how Notion databases are structured and queried
Content Sync Process
Deep dive into the pre-build synchronization pipeline
Multi-Framework Strategy
Understand when to use Astro, React, or Solid.js