Live
fuschimp_transparent.png
Fusite

My little corner of the internet showcasing personal projects and updates about my work

Node.js Serverless Handlebars Bun AWS

Frontend Tech Design Doc

Overview

Fusite Web is a custom-built static site generator that transforms Notion databases into a high-performance, SEO-optimized personal website. Rather than using conventional static site generators like Hugo or Jekyll, this system is purpose-built to leverage Notion as a headless CMS, providing a seamless content management experience while delivering blazing-fast static HTML.

Key Characteristics

  • Notion-First Content Management: All content lives in Notion databases, enabling collaborative editing and structured data management
  • Build-Time Static Generation: Pre-renders all pages to static HTML at build time (no runtime server required)
  • Serverless Deployment: Hosted on AWS S3 + CloudFront for global CDN distribution
  • TypeScript & Bun: Modern tooling for type safety and performance
  • Zero Client-Side JavaScript: Content pages are pure HTML/CSS for maximum performance and accessibility
  • Why Custom-Built?

    Traditional static site generators require content in markdown files or proprietary formats. By building a custom generator, Fusite achieves:

  • Dynamic Content Relationships: Notion’s relational databases enable complex content hierarchies (projects → documentation → changelogs)
  • Real-Time Editing: Update content in Notion and rebuild—no git commits or markdown wrangling
  • Structured Data: Native support for structured content types with proper typing
  • Tailored Architecture: Optimized specifically for the fuscripts ecosystem and personal project showcase needs

  • Technology Stack

    Core Runtime & Build Tools

    TechnologyVersionPurpose
    Bun1.0.0+JavaScript runtime with built-in bundler and fast package management
    TypeScript5.6+Type-safe development with strict mode enabled
    Handlebars4.7.8Server-side HTML templating engine
    PostCSS8.4.47CSS processing pipeline

    Frontend Technologies

    TechnologyPurpose
    Tailwind CSSUtility-first CSS framework for responsive design
    PrismJSSyntax highlighting for code blocks (supports 30+ languages)
    @tailwindcss/typographyBeautiful typographic defaults for content
    @tailwindcss/formsConsistent form styling

    AWS Infrastructure

    ServicePurpose
    S3Static file hosting
    CloudFrontGlobal CDN distribution
    AWS SDK v3Programmatic deployment and cache invalidation

    Notion Integration

    PackagePurpose
    fusite-commonsShared library for Notion API integration, type definitions, and content transformation
    @notionhq/clientOfficial Notion SDK (abstracted through fusite-commons)

    System Architecture

    High-Level Data Flow

    plain text
    ┌─────────────────┐
    │  Notion         │
    │  Databases      │
    │  - Projects     │
    │  - Chronicles   │
    │  - About Page   │
    └────────┬────────┘
             │
             ▼
    ┌─────────────────┐
    │  fusite-commons │
    │  NotionService  │
    │  - API calls    │
    │  - Transforms   │
    └────────┬────────┘
             │
             ▼
    ┌─────────────────┐
    │  DataService    │
    │  - Caching      │
    │  - Aggregation  │
    └────────┬────────┘
             │
             ▼
    ┌─────────────────┐
    │  SiteBuilder    │
    │  Orchestrator   │
    └────────┬────────┘
             │
             ├─────────────┬─────────────┬─────────────┬─────────────┐
             ▼             ▼             ▼             ▼             ▼
       Route Gen     Template      CSS Process   Asset Copy    SEO Gen
       (URLs)        Rendering     (Tailwind)    (Static)      (Sitemap)
             │             │             │             │             │
             └─────────────┴─────────────┴─────────────┴─────────────┘
                                         │
                                         ▼
                                  ┌─────────────┐
                                  │   /dist     │
                                  │ Static HTML │
                                  └──────┬──────┘
                                         │
                                         ▼
                                  ┌─────────────┐
                                  │  S3 Bucket  │
                                  └──────┬──────┘
                                         │
                                         ▼
                                  ┌─────────────┐
                                  │ CloudFront  │
                                  │ Global CDN  │
                                  └─────────────┘


    Data Sources & Content Model

    Notion Database Structure

    Fusite sources all content from four Notion databases:

    1. Projects Database

    Properties:

  • title (Title): Project name
  • slug (Rich Text): URL-safe identifier
  • description (Rich Text): Short project summary
  • status (Select): Active | Completed | On Hold | Archived
  • tags (Multi-select): Technology/category tags
  • startDate (Date): Project start
  • endDate (Date): Project completion
  • featuredImage (URL): Open Graph image
  • public (Checkbox): Visibility in dev environment
  • Content:

  • Project body stored as Notion page blocks
  • Supports all Notion block types (headings, paragraphs, lists, code, images, etc.)
  • 2. Fuschronicles Database (Development Logs)

    Properties:

  • title (Title): Chronicle/post title
  • slug (Rich Text): URL identifier
  • excerpt (Rich Text): Brief summary
  • type (Select): documentation | changelog | log
  • publishedDate (Date): Publication timestamp
  • featuredImage (URL): Cover image
  • relatedProject (Relation): Links to Projects database
  • Content Types:

  • Documentation: Project-specific docs (nested under /projects/{slug}/docs/{docSlug})
  • Changelog: Version history (shown in project timelines)
  • Work Log: General development blog posts (appear in main /fuschronicles feed)
  • 3. About Page

    Structure:

  • Single Notion page (not a database)
  • Contains bio, skills, contact information
  • Rendered as /about route

  • Static Site Structure

    Generated Directory Layout

    The build process creates this structure in /dist:

    plain text
    dist/
    ├── index.html                          # Home page (/)
    ├── about/
    │   └── index.html                      # About page (/about)
    ├── projects/
    │   ├── index.html                      # Projects listing (/projects)
    │   ├── fusclock/
    │   │   ├── index.html                  # Project detail (/projects/fusclock)
    │   │   ├── docs/
    │   │   │   ├── getting-started/
    │   │   │   │   └── index.html          # Documentation (/projects/fusclock/docs/getting-started)
    │   │   │   └── api-reference/
    │   │   │       └── index.html
    │   │   └── timeline/
    │   │       └── index.html              # Changelogs (/projects/fusclock/timeline)
    │   ├── fuschantt/
    │   │   └── ...
    │   └── [other-projects]/
    ├── fuschronicles/
    │   ├── index.html                      # Chronicles listing (/fuschronicles)
    │   ├── 2025-01-15-new-feature/
    │   │   └── index.html                  # Individual chronicle (/fuschronicles/2025-01-15-new-feature)
    │   └── [other-chronicles]/
    ├── assets/
    │   ├── css/
    │   │   └── main.css                    # Compiled and minified CSS
    │   ├── images/
    │   │   ├── fuschimp_transparent.png
    │   │   ├── favicon-16x16.png
    │   │   ├── favicon-32x32.png
    │   │   └── og-default.jpg
    ├── sitemap.xml                         # SEO sitemap
    ├── robots.txt                          # Search engine directives
    └── 404.html                            # Error page (optional)

    Route Generation Logic

    Routes are generated programmatically by the SiteBuilder:

    Static Routes

    RouteTemplatePurpose
    /home-direct.hbsLanding page with featured projects and latest chronicles
    /aboutabout-direct.hbsPersonal bio and information
    /projectsprojects-direct.hbsComplete project portfolio listing
    /fuschroniclesfuschronicles-direct.hbsDevelopment log feed (excludes documentation type)

    Dynamic Routes (Generated per Content Item)

    Projects:

  • /projects/{slug}project-complete.hbs
    • Full project details
    • Related chronicles sidebar
    • Documentation links
  • Project Documentation:

  • /projects/{slug}/docs/{docSlug}project-doc.hbs
    • Documentation content
    • Project navigation sidebar
    • Breadcrumb navigation
  • Project Timelines:

  • /projects/{slug}/timelinetimeline.hbs
    • Chronological changelogs for the project
    • Documentation sidebar
  • Chronicles:

  • /fuschronicles/{slug}fuschronicle-complete.hbs
    • Full chronicle content
    • Previous/Next navigation
    • Related project links
  • URL Slug Generation

    Slugs are generated from Notion properties using the slug.ts utility:

    typescript
    // Helper functions available in templates
    projectUrl(slug) // → /projects/{slug}
    chronicleUrl(slug) // → /fuschronicles/{slug}
    projectTimelineUrl(slug) // → /projects/{slug}/timeline
    projectDocUrl(projectSlug, docSlug) // → /projects/{slug}/docs/{docSlug}
    projectDocsUrl(slug) // → /projects/{slug}/docs

    Slug Rules:

  • Lowercase alphanumeric + hyphens
  • No special characters
  • Trailing slashes omitted (handled by directory index.html)
  • Manually defined in Notion (not auto-generated from title)

  • Build Pipeline

    Build Process Flow

    The build transforms Notion content into a production-ready static website in 8 sequential phases:

  • Configuration & Environment Setup — Load environment variables and validate required settings
  • Data Fetching — Fetch all content from Notion databases in parallel with in-memory caching
  • Route Generation — Create route definitions for every page with SEO metadata
  • Template Rendering — Compile Handlebars templates to HTML with custom helpers and partials
  • CSS Processing — Transform Tailwind CSS into optimized, purged stylesheets
  • Static Asset Management — Copy images, fonts, and other static files to output directory
  • SEO & Metadata Generation — Generate sitemap.xml, robots.txt, and Open Graph tags
  • Build Completion — Output build statistics and validate results
  • Typical build time: 3-5 seconds → produces /dist directory ready for S3 + CloudFront deployment


    Key Architectural Decisions

    1. Notion as Primary CMS

    Rationale:

  • Familiar Interface: Notion’s editing experience is superior to markdown/CMS admin panels
  • Relational Data: Native support for content relationships (projects ↔︎ chronicles)
  • Structured Content: Database properties provide type-safe metadata
  • Collaborative Editing: Can share databases with collaborators
  • Mobile-Friendly: Edit content from Notion mobile app
  • Trade-offs:

  • Requires internet connection for content updates
  • Build time depends on Notion API performance
  • Vendor lock-in (mitigated by export capabilities)
  • 2. Static Generation over SSR

    Rationale:

  • Performance: Pre-rendered HTML is instant (no server processing)
  • Cost: S3 + CloudFront is cheaper than running servers
  • Scalability: CDN handles traffic spikes automatically
  • Security: No server-side attack surface
  • Simplicity: No runtime dependencies or server management
  • Trade-offs:

  • Content updates require rebuild (acceptable for personal site)
  • No dynamic features (comments, search) without client-side JS or external services
  • Build time increases with content volume
  • 3. Handlebars over React/Vue/Svelte

    Rationale:

  • Zero Client JS: Pure HTML/CSS for maximum performance
  • Simplicity: Template logic is straightforward
  • Build Performance: Faster compilation than heavy frameworks
  • SEO: No hydration or client-side routing complexity
  • Trade-offs:

  • No interactive components without custom JS
  • Less powerful than component-based frameworks
  • Template reuse via partials (less elegant than component composition)
  • 4. Multi-Environment Architecture

    Rationale:

  • Development Safety: Test changes on dev.fuscripts.com before production
  • Content Separation: Use dev Notion database for drafts
  • Local Iteration: Build locally without affecting production
  • Implementation:

  • Separate Notion databases per environment
  • Environment-specific S3 buckets and CloudFront distributions
  • .env.{environment} files for configuration
  • 5. In-Memory Caching (Not Persistent)

    Rationale:

  • Build Speed: Prevents duplicate Notion API calls within single build
  • Simplicity: No cache invalidation logic needed
  • Fresh Content: Every build fetches latest data (scheduled rebuilds keep site fresh)
  • Trade-offs:

  • No incremental builds (every build is full rebuild)
  • Build time grows with content (mitigated by Notion API speed and caching)

  • Analytics

    PostHog Integration

    Analytics Setup:

  • Provider: PostHog (privacy-first, self-hostable)
  • Client: PostHog JavaScript SDK (loaded asynchronously)
  • Configuration: Cookie-less tracking (persistence: 'memory')
  • Privacy Considerations:

  • No persistent cookies (session data resets on page refresh)
  • No cross-site tracking
  • Respects Do Not Track headers (optional)
  • Tracked Events:

  • Page views
  • Separate Projects:

  • Dev environment: PostHog project for testing
  • Prod environment: Separate PostHog project for real analytics

  • Lessons Learned

    Successes

  • Notion integration is seamless and provides excellent editing UX
  • Handlebars is sufficient for simple templating needs
  • Static generation + CDN is incredibly fast and cheap
  • TypeScript catches many bugs at build time
  • Challenges

  • Notion API rate limits require careful request management
  • Handlebars partials are less elegant than component composition
  • Build time grows linearly with content (incremental builds needed)
  • No hot reload (manual rebuilds during development)

  • Summary

    Fusite Web demonstrates that custom static site generators can be simple, fast, and powerful. By leveraging Notion as a headless CMS and deploying to AWS infrastructure, the system achieves:

  • Excellent editing UX (Notion’s interface)
  • Fast page loads (static HTML + global CDN)
  • Low cost (S3 + CloudFront pennies/month)
  • Type safety (full TypeScript)
  • Maintainability (clear architecture, modular code)
  • Scalability (handles hundreds of pages efficiently)
  • The architecture is well-suited for personal websites, portfolios, and documentation sites where content updates are infrequent and build times are acceptable. For high-frequency content updates or very large sites (1000+ pages), consider adding incremental builds or switching to server-side rendering.


    Appendix

    File Structure Reference

    plain text
    fusite/web/
    ├── src/
    │   ├── build/
    │   │   ├── cli.ts                    # Build CLI entry point
    │   │   ├── builder.ts                # Main build orchestrator
    │   │   ├── renderer.ts               # Handlebars template engine
    │   │   ├── css-processor.ts          # PostCSS/Tailwind pipeline
    │   │   └── validate.ts               # Build validation (planned)
    │   ├── services/
    │   │   └── data.ts                   # Notion data fetching & caching
    │   ├── dev/
    │   │   ├── server.ts                 # HTTP dev server
    │   │   └── serve.ts                  # CLI for dev server
    │   ├── utils/
    │   │   ├── config.ts                 # Configuration management
    │   │   ├── logger.ts                 # Structured logging
    │   │   ├── slug.ts                   # URL slug utilities
    │   │   ├── date.ts                   # Date formatting
    │   │   └── syntax-highlighter.ts     # PrismJS integration
    │   ├── templates/
    │   │   ├── layouts/
    │   │   │   ├── base.hbs              # Root HTML layout
    │   │   │   ├── page.hbs              # Page wrapper
    │   │   │   └── article.hbs           # Article layout
    │   │   ├── pages/
    │   │   │   ├── home-direct.hbs       # Home page
    │   │   │   ├── about-direct.hbs      # About page
    │   │   │   ├── projects-direct.hbs   # Projects listing
    │   │   │   ├── project-complete.hbs  # Project detail
    │   │   │   ├── project-doc.hbs       # Documentation page
    │   │   │   ├── timeline.hbs          # Project timeline
    │   │   │   ├── fuschronicles-direct.hbs  # Chronicles listing
    │   │   │   ├── fuschronicle-complete.hbs # Chronicle detail
    │   │   │   └── 404.hbs               # Error page
    │   │   └── partials/
    │   │       ├── header.hbs            # Site header/nav
    │   │       ├── footer.hbs            # Site footer
    │   │       ├── project-card.hbs      # Project card component
    │   │       ├── fuschronicle-card.hbs # Chronicle card
    │   │       └── project-about-navigation.hbs  # Project sidebar
    │   ├── styles/
    │   │   ├── main.css                  # Tailwind entry point
    │   │   ├── components.css            # Custom components
    │   │   ├── utilities.css             # Custom utilities
    │   │   └── syntax-highlighting.css   # PrismJS theme
    │   └── assets/
    │       └── images/
    │           ├── fuschimp_transparent.png
    │           ├── favicon-*.png
    │           └── og-default.jpg
    ├── dist/                             # Generated site (gitignored)
    ├── cache/                            # Build cache (gitignored)
    ├── .env                              # Base configuration
    ├── .env.local                        # Local secrets (gitignored)
    ├── .env.dev                          # Dev environment
    ├── .env.prod                         # Prod environment
    ├── package.json                      # Dependencies & scripts
    ├── tsconfig.json                     # TypeScript configuration
    ├── tailwind.config.js                # Tailwind settings
    ├── postcss.config.js                 # PostCSS configuration
    └── bun.lock                          # Bun lockfile