Fusite Web Low-Level Design

Design Documents
Last updated August 23, 2025

Fusite Web Low-Level Design

Based on the Fusite High-Level Design and API Low-Level Design documents, this low-level design outlines the web repository implementation for static site generation that consumes the Fusite API.

1. Overview

The Fusite Web application serves as a static site generator that:

  1. Shared Notion Access: Uses the fusite-commons package for Notion SDK access
  2. Static Site Generator: Builds HTML pages from Notion data
  3. Asset Pipeline: Processes and optimizes images and assets
  4. Deployment System: Uploads generated site to S3 and invalidates CloudFront

The web application is built with:

  • Runtime: Bun for build tooling and package management
  • Framework: Vanilla TypeScript (no heavy frontend framework)
  • Templates: Handlebars for HTML templating
  • Styling: Tailwind CSS for styling
  • Data Source: Shared fusite-commons package (same as API)
  • Deployment: AWS S3 + CloudFront
  • Build Environment: Local builds (GitHub Actions or local machine)

2. Project Structure

/web
    bunfig.toml              # Bun configuration
    package.json             # Dependencies and scripts (includes fusite-commons)
    tsconfig.json            # TypeScript configuration
    tailwind.config.js       # Tailwind CSS configuration
    /src
    /templates             # HTML template system
    layouts/             # Base layouts
    base.hbs           # Main HTML layout
    page.hbs           # Standard page layout
    article.hbs        # Article-specific layout
    pages/               # Page templates
    home.hbs           # Homepage template
    about.hbs          # About page template
    projects.hbs       # Projects listing template
    project.hbs        # Individual project template
    chronicles.hbs     # Fuschronicles listing template (maps to /fuschronicles)
    chronicle.hbs      # Individual fuschronicle template (maps to /fuschronicles/{slug})
    404.hbs           # 404 error page
    partials/            # Reusable components
    header.hbs         # Site header
    footer.hbs         # Site footer
    nav.hbs           # Navigation
    project-card.hbs   # Project card component
    chronicle-card.hbs # Chronicle card component
    breadcrumbs.hbs    # Breadcrumb navigation
    /styles               # CSS and styling
    main.css            # Main stylesheet (Tailwind entry)
    components.css      # Custom component styles
    utilities.css       # Custom utility classes
    /assets              # Static assets
    /images            # Static images (logos, icons)
    /fonts             # Web fonts
    /build               # Build system
    builder.ts         # Main build orchestrator
    renderer.ts        # Template rendering engine
    seo.ts            # SEO metadata generation
    sitemap.ts        # Sitemap.xml generation
    robots.ts         # Robots.txt generation
    feed.ts           # RSS feed generation
    assets.ts         # Asset processing and optimization
    deploy.ts         # S3 deployment and CloudFront invalidation
    cache.ts          # Build-time caching layer
    /transformers        # Web-specific transformers
    notionToHtml.ts    # Convert Notion content to HTML for static pages
    imageProcessor.ts  # Process and optimize images for web
    /utils               # Shared utilities
    config.ts          # Build configuration
    logger.ts          # Build logging
    slug.ts            # Slug utilities
    date.ts            # Date formatting utilities
    markdown.ts        # Markdown processing (if needed)
    /routes              # Route generation
    generator.ts       # Route mapping and generation
    types.ts           # Route type definitions
    /dist                  # Generated site output (gitignored)
    /cache                 # Build cache directory (gitignored)
    /.github
    /workflows
    build-deploy.yml   # GitHub Actions workflow
    
    /commons                 # Shared package between API and Web
    package.json           # Commons package definition
    index.ts              # Main exports
    /src
    /notion             # Notion SDK access layer
    client.ts         # Notion SDK initialization
    repositories/     # Data access layer
    project.ts      # Project database operations
    task.ts         # Task database operations
    chronicle.ts    # Chronicle database operations
    about.ts        # About page operations
    services/         # Business logic layer
    projectService.ts  # Project business logic
    taskService.ts     # Task business logic
    chronicleService.ts # Chronicle business logic
    contentService.ts  # Content transformation logic
    transformers/     # Base data transformers
    notionToApi.ts  # Convert Notion blocks to API format
    slugGenerator.ts # Generate and validate slugs
    types.ts          # Shared type definitions
    /utils              # Shared utilities
    validation.ts     # Input validation schemas
    errors.ts         # Custom error classes

3. Data Types and Notion Integration

3. Data Models and Types

3.1 Shared Data Types from Commons

The Web project uses shared data types from the fusite-commons package:

typescript
// Import shared types from commons package
    import {
    Project,
    Fuschronicle,
    ProjectTask,
    AboutPage,
    ProjectStatus,
    ChronicleType,
    TaskStatus,
    NotionBlock,
    } from "fusite-commons"
    
    // Web-specific types for static generation
    export interface HtmlContent {
    type:
    | "heading_1"
    | "heading_2"
    | "heading_3"
    | "paragraph"
    | "bulleted_list_item"
    | "numbered_list_item"
    | "code"
    | "quote"
    | "image"
    | "video"
    | "file"
    content: string
    html: string // Pre-rendered HTML
    annotations?: {
    bold?: boolean
    italic?: boolean
    strikethrough?: boolean
    underline?: boolean
    code?: boolean
    color?: string
    }
    href?: string
    src?: string
    alt?: string
    caption?: string
    }

3.2 Build-Time Data Types

typescript
// Site data aggregated for build
    interface SiteData {
    about: AboutPage
    projects: Project[]
    chronicles: Fuschronicle[]
    tasks: Map<string, ProjectTask[]> // keyed by project ID
    }
    
    // Page generation context
    interface PageContext {
    title: string
    description: string
    canonical: string
    openGraph: OpenGraphMeta
    structuredData?: StructuredData
    breadcrumbs?: Breadcrumb[]
    lastModified?: string
    }
    
    // Route definition
    interface Route {
    path: string
    template: string
    data: any
    context: PageContext
    priority: number
    changeFreq: "daily" | "weekly" | "monthly" | "yearly"
    }
    
    // Build result
    interface BuildResult {
    success: boolean
    routes: Route[]
    assets: GeneratedAsset[]
    errors: BuildError[]
    buildTime: number
    cacheHits: number
    cacheMisses: number
    }
    
    interface GeneratedAsset {
    source: string
    destination: string
    size: number
    hash: string
    }
    
    interface ProjectMetrics {
    totalTasks: number
    completedTasks: number
    totalEstimatedTime: number
    totalActualTime: number
    firstTaskDate?: string
    lastTaskDate?: string
    completionRate: number
    averageTaskTime: number
    }

4. Core Components

4.1 Shared Notion Data Access

The Web project uses the fusite-commons package for all Notion data access, providing:

  • Repositories: Project, Task, Chronicle, About page data access
  • Services: Business logic and content transformation
  • Transformers: Convert Notion blocks to web-ready format
  • Types: Shared data structures across API and Web
typescript
// Import shared components
    import {
    ProjectService,
    ChronicleService,
    TaskService,
    AboutService,
    } from "fusite-commons"
    
    class WebBuilder {
    constructor(
    private projectService: ProjectService,
    private chronicleService: ChronicleService,
    private taskService: TaskService,
    private aboutService: AboutService
    ) {}
    
    async buildSite() {
    const projects = await this.projectService.getPublicProjects()
    const chronicles = await this.chronicleService.getPublishedChronicles()
    // Transform and render...
    }
    }

4.2 Web-Specific Transformers (/src/transformers/)

notionToHtml.ts

typescript
import { notionToApi } from "fusite-commons"
    
    export class NotionToHtmlTransformer {
    constructor(private imageProcessor: ImageProcessor)
    
    async transformBlocks(blocks: NotionBlock[]): Promise<HtmlContent[]>
    async processImages(content: HtmlContent[]): Promise<HtmlContent[]>
    
    private transformBlock(block: NotionBlock): HtmlContent
    private renderToHtml(content: HtmlContent): string
    private processImageBlock(block: NotionBlock): Promise<HtmlContent>
    private applyAnnotations(text: string, annotations: any): string
    }

imageProcessor.ts

typescript
export class ImageProcessor {
    async optimizeImage(imageUrl: string): Promise<OptimizedImage>
    async generateResponsiveImages(imageUrl: string): Promise<ResponsiveImage[]>
    async downloadAndOptimize(url: string, outputPath: string): Promise<string>
    
    private compressImage(buffer: Buffer, quality: number): Promise<Buffer>
    private generateSrcSet(images: ResponsiveImage[]): string
    }

4.3 Template Rendering (/src/templates/)

renderer.ts

typescript
export class TemplateRenderer {
    private handlebars: HandlebarsEnvironment
    private templatesDir: string
    
    constructor(templatesDir: string)
    
    async render(
    template: string,
    data: any,
    context: PageContext
    ): Promise<string>
    async renderPage(route: Route): Promise<string>
    
    // Specific page renderers
    async renderHome(data: HomePageData, context: PageContext): Promise<string>
    async renderAbout(about: AboutPage, context: PageContext): Promise<string>
    async renderProjects(
    projects: Project[],
    context: PageContext
    ): Promise<string>
    async renderProject(
    project: Project,
    related: RelatedData,
    context: PageContext
    ): Promise<string>
    async renderChronicles(
    chronicles: Fuschronicle[],
    context: PageContext
    ): Promise<string>
    async renderChronicle(
    chronicle: Fuschronicle,
    navigation: NavigationData,
    context: PageContext
    ): Promise<string>
    async render404(context: PageContext): Promise<string>
    
    private registerHelpers(): void
    private registerPartials(): void
    private processContent(content: HtmlContent[]): string
    }
    
    interface HomePageData {
    featuredProjects: Project[]
    latestChronicles: Fuschronicle[]
    about?: AboutPage
    }
    
    interface RelatedData {
    chronicles: Fuschronicle[]
    tasks?: ProjectTask[]
    metrics?: ProjectMetrics
    }
    
    interface NavigationData {
    prev?: Fuschronicle
    next?: Fuschronicle
    }

4.4 Build System (/src/build/)

builder.ts

typescript
import {
    ProjectService,
    ChronicleService,
    TaskService,
    AboutService,
    } from "fusite-commons"
    
    export class SiteBuilder {
    private projectService: ProjectService
    private chronicleService: ChronicleService
    private taskService: TaskService
    private aboutService: AboutService
    private renderer: TemplateRenderer
    private cache: BuildCache
    private config: BuildConfig
    
    constructor(config: BuildConfig)
    
    async buildSite(): Promise<BuildResult>
    async buildPage(route: Route): Promise<void>
    
    private async loadSiteData(): Promise<SiteData>
    private async generateRoutes(data: SiteData): Promise<Route[]>
    private async processAssets(): Promise<GeneratedAsset[]>
    private async writeStaticFiles(
    routes: Route[],
    assets: GeneratedAsset[]
    ): Promise<void>
    private async generateSitemap(routes: Route[]): Promise<void>
    private async generateRobotsTxt(): Promise<void>
    private async generateRSSFeed(chronicles: Fuschronicle[]): Promise<void>
    }
    
    interface BuildConfig {
    outputDir: string
    cacheDir: string
    assetsDir: string
    templatesDir: string
    publicUrl: string
    enableCache: boolean
    }

seo.ts

typescript
export class SEOGenerator {
    private siteConfig: SiteConfig
    
    constructor(siteConfig: SiteConfig)
    
    generatePageMeta(data: PageMetaInput): PageContext
    generateOpenGraph(data: OGInput): OpenGraphMeta
    generateStructuredData(data: StructuredDataInput): StructuredData
    generateBreadcrumbs(path: string, data?: any): Breadcrumb[]
    
    // Page-specific SEO generators
    generateProjectSEO(project: Project): PageContext
    generateChronicleSeO(chronicle: Fuschronicle): PageContext
    generateListingSEO(type: "projects" | "chronicles", data: any[]): PageContext
    }
    
    interface SiteConfig {
    siteName: string
    siteDescription: string
    siteUrl: string
    author: string
    twitterHandle?: string
    defaultImage?: string
    }
    
    interface OpenGraphMeta {
    title: string
    description: string
    image: string
    url: string
    type: string
    siteName: string
    }
    
    interface StructuredData {
    "@context": string
    "@type": string
    [key: string]: any
    }
    
    interface Breadcrumb {
    name: string
    url: string
    }

4.4 Route Generation (/src/routes/)

generator.ts

typescript
export class RouteGenerator {
    private seoGenerator: SEOGenerator
    
    constructor(seoGenerator: SEOGenerator)
    
    async generateAllRoutes(data: SiteData): Promise<Route[]>
    
    // Static routes
    generateHomeRoute(data: SiteData): Route
    generateAboutRoute(about: AboutPage): Route
    generateProjectsRoute(projects: Project[]): Route
    generateFuschroniclesRoute(chronicles: Fuschronicle[]): Route // Maps to /fuschronicles
    generate404Route(): Route
    
    // Dynamic routes
    generateProjectRoutes(projects: Project[], data: SiteData): Route[]
    // Generates:
    // - /projects/{slug} (main project page)
    // - /projects/{slug}/timeline (chronicles filtered by project relation)
    // - /projects/{slug}/backlog (optional, from project tasks)
    generateFuschronicleRoutes(chronicles: Fuschronicle[]): Route[] // Maps to /fuschronicles/{slug}
    
    private calculatePriority(path: string): number
    private getChangeFrequency(
    path: string
    ): "daily" | "weekly" | "monthly" | "yearly"
    }

4.6 Asset Processing (/src/build/assets.ts)

typescript
export class AssetProcessor {
    private config: AssetConfig
    
    constructor(config: AssetConfig)
    
    async processImages(content: HtmlContent[]): Promise<ProcessedAsset[]>
    async optimizeImage(imageUrl: string): Promise<OptimizedImage>
    async generateResponsiveImages(imageUrl: string): Promise<ResponsiveImageSet>
    async copyStaticAssets(): Promise<GeneratedAsset[]>
    async generateAssetManifest(assets: GeneratedAsset[]): Promise<void>
    
    private downloadImage(url: string): Promise<Buffer>
    private resizeImage(buffer: Buffer, sizes: number[]): Promise<ResizedImage[]>
    private uploadToS3(asset: ProcessedAsset): Promise<string>
    }
    
    interface AssetConfig {
    staticDir: string
    outputDir: string
    s3Bucket: string
    s3Prefix: string
    imageSizes: number[]
    imageQuality: number
    }
    
    interface ProcessedAsset {
    original: string
    optimized: string
    formats: string[]
    sizes: { width: number; height: number; url: string }[]
    }

5. Build Pipeline

5.1 Build Process Flow

1. Load Configuration
    ├── Shared commons package configuration
    ├── Build settings
    └── Deployment config
    
    2. Fetch Data from Notion (via fusite-commons)
    ├── Projects (public only)
    ├── Fuschronicles (all)
    ├── Tasks (by project)
    └── About page
    
    3. Process Content
    ├── Transform Notion blocks to HTML
    ├── Download and optimize images
    ├── Generate responsive image sets
    └── Process static assets
    
    4. Generate Routes (following high-level design route map)
    ├── Static routes (/, /about, /projects, /fuschronicles)
    ├── Dynamic project routes (/projects/{slug}, /projects/{slug}/timeline, /projects/{slug}/backlog)
    ├── Dynamic fuschronicle routes (/fuschronicles/{slug})
    └── Error pages (/404)
    
    5. Render Pages
    ├── Apply templates with data
    ├── Generate SEO metadata
    ├── Process internal links
    └── Optimize HTML
    
    6. Generate Supplementary Files
    ├── Sitemap.xml
    ├── Robots.txt
    ├── RSS feed
    └── Asset manifest
    
    7. Deploy to S3
    ├── Upload HTML files
    ├── Upload processed assets
    ├── Set appropriate headers
    └── Invalidate CloudFront cache

5.2 Build Scripts

package.json scripts:

json
{
    "scripts": {
    "build": "bun run src/build/cli.ts",
    "build:dev": "bun run src/build/cli.ts --env=dev",
    "build:prod": "bun run src/build/cli.ts --env=prod",
    "deploy": "bun run build:prod && bun run src/build/deploy.ts",
    "dev": "bun run build:dev && bun run serve",
    "serve": "bun run src/dev/server.ts",
    "preview": "bun run build && bun run serve",
    "clean": "rm -rf dist cache",
    "validate": "bun run src/build/validate.ts"
    }
    }

6. Configuration

6.1 Environment Variables

bash
# Notion Configuration (handled by fusite-commons package)
    # See API Low-Level Design for complete Notion configuration details
    NOTION_TOKEN=secret_xxx
    NOTION_DATABASE_ABOUT=xxx
    NOTION_DATABASE_PROJECT=xxx
    NOTION_DATABASE_TASK=xxx
    NOTION_DATABASE_CHRONICLE=xxx
    
    # Site Configuration (Web-specific)
    SITE_URL=https://fuscripts.com
    SITE_NAME="Fuscripts"
    SITE_DESCRIPTION="Personal projects and development logs"
    SITE_AUTHOR="Fusca"
    
    # Build Configuration (Web-specific)
    BUILD_ENV=production
    ENABLE_CACHE=true
    CACHE_TTL_HOURS=6
    
    # AWS Configuration (for deployment)
    AWS_REGION=us-east-1
    S3_BUCKET=fusite-static
    S3_ASSETS_PREFIX=assets/
    CLOUDFRONT_DISTRIBUTION_ID=optional_cf_id
    
    # Development
    DEV_PORT=3000
    DEV_OPEN_BROWSER=true

6.2 Build Configuration

build.config.ts

typescript
// Note: Notion configuration is handled by fusite-commons package
    export interface BuildConfig {
    site: {
    url: string
    name: string
    description: string
    author: string
    defaultImage: string
    }
    build: {
    outputDir: string
    cacheDir: string
    enableCache: boolean
    cacheTtl: number
    }
    assets: {
    staticDir: string
    s3Bucket: string
    s3Prefix: string
    imageSizes: number[]
    imageQuality: number
    }
    deployment: {
    s3Bucket: string
    cloudfrontDistId?: string
    invalidatePaths: string[]
    }
    }

7. Templates and Styling

7.1 Handlebars Helpers

typescript
// Custom Handlebars helpers
    export function registerHelpers(handlebars: HandlebarsEnvironment): void {
    // Date formatting
    handlebars.registerHelper("formatDate", (date: string, format: string) => {
    return formatDate(new Date(date), format)
    })
    
    // Content rendering
    handlebars.registerHelper("renderContent", (content: HtmlContent[]) => {
    return new handlebars.SafeString(processHtmlContent(content))
    })
    
    // URL helpers
    handlebars.registerHelper("projectUrl", (slug: string) => {
    return `/projects/${slug}`
    })
    
    handlebars.registerHelper("chronicleUrl", (slug: string) => {
    return `/chronicles/${slug}`
    })
    
    // Conditional helpers
    handlebars.registerHelper("ifEquals", (a: any, b: any, options: any) => {
    return a === b ? options.fn(this) : options.inverse(this)
    })
    
    // Asset helpers
    handlebars.registerHelper("assetUrl", (path: string) => {
    return `/assets/${path}`
    })
    }

7.2 Tailwind Configuration

tailwind.config.js

javascript
module.exports = {
    content: ["./src/templates/**/*.hbs", "./src/styles/**/*.css"],
    theme: {
    extend: {
    colors: {
    primary: {
    50: "#f0f9ff",
    500: "#3b82f6",
    900: "#1e3a8a",
    },
    },
    typography: {
    DEFAULT: {
    css: {
    maxWidth: "none",
    color: "#374151",
    a: {
    color: "#3b82f6",
    "&:hover": {
    color: "#1d4ed8",
    },
    },
    },
    },
    },
    },
    },
    plugins: [require("@tailwindcss/typography"), require("@tailwindcss/forms")],
    }

8. Deployment and CI/CD

8.1 GitHub Actions Workflow

.github/workflows/build-deploy.yml

yaml
name: Build and Deploy Fusite
    
    on:
    push:
    branches: [main]
    schedule:
    - cron: "0 */6 * * *" # Rebuild every 6 hours
    workflow_dispatch:
    
    jobs:
    build-deploy:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - uses: oven-sh/setup-bun@v1
    with:
    bun-version: latest
    
    - name: Install dependencies
    run: bun install
    
    - name: Build site
    env:
    NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
    NOTION_DATABASE_ABOUT: ${{ secrets.NOTION_DATABASE_ABOUT }}
    NOTION_DATABASE_PROJECT: ${{ secrets.NOTION_DATABASE_PROJECT }}
    NOTION_DATABASE_TASK: ${{ secrets.NOTION_DATABASE_TASK }}
    NOTION_DATABASE_CHRONICLE: ${{ secrets.NOTION_DATABASE_CHRONICLE }}
    SITE_URL: ${{ vars.SITE_URL }}
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    run: bun run build:prod
    
    - name: Deploy to S3
    env:
    AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
    AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    S3_BUCKET: ${{ vars.S3_BUCKET }}
    CLOUDFRONT_DISTRIBUTION_ID: ${{ vars.CLOUDFRONT_DISTRIBUTION_ID }}
    run: bun run deploy

8.2 Deployment Script

src/build/deploy.ts

typescript
export class Deployer {
    private s3Client: S3Client
    private cloudfrontClient?: CloudFrontClient
    private config: DeploymentConfig
    
    constructor(config: DeploymentConfig)
    
    async deployToS3(buildDir: string): Promise<DeploymentResult>
    async invalidateCloudFront(paths: string[]): Promise<void>
    
    private async uploadFile(filePath: string, key: string): Promise<void>
    private getContentType(filePath: string): string
    private getCacheControl(filePath: string): string
    private async syncDirectory(
    localDir: string,
    s3Prefix: string
    ): Promise<UploadResult[]>
    }
    
    interface DeploymentResult {
    success: boolean
    filesUploaded: number
    errors: DeploymentError[]
    invalidationId?: string
    deploymentTime: number
    }

9. Development Workflow

9.1 Development Server

typescript
// src/dev/server.ts
    export class DevServer {
    private port: number
    private buildDir: string
    
    constructor(config: DevConfig)
    
    async start(): Promise<void>
    async rebuild(): Promise<void>
    async watchForChanges(): Promise<void>
    
    private serveStatic(request: Request): Response
    private handle404(): Response
    }

9.2 Build Validation

typescript
// src/build/validate.ts
    export class BuildValidator {
    async validateBuild(buildDir: string): Promise<ValidationResult>
    
    private async checkHtmlValidity(
    htmlFiles: string[]
    ): Promise<ValidationError[]>
    private async checkInternalLinks(htmlFiles: string[]): Promise<LinkError[]>
    private async checkAssets(assetFiles: string[]): Promise<AssetError[]>
    private async checkSEO(htmlFiles: string[]): Promise<SEOError[]>
    }

10. Error Handling and Monitoring

10.1 Build Error Types

typescript
export class BuildError extends Error {
    constructor(message: string, public phase: BuildPhase, public details?: any) {
    super(message)
    }
    }
    
    export class ApiError extends BuildError {
    constructor(message: string, public endpoint: string, details?: any) {
    super(message, "api-fetch", details)
    }
    }
    
    export class TemplateError extends BuildError {
    constructor(message: string, public template: string, details?: any) {
    super(message, "template-render", details)
    }
    }
    
    export class AssetError extends BuildError {
    constructor(message: string, public asset: string, details?: any) {
    super(message, "asset-process", details)
    }
    }
    
    type BuildPhase =
    | "config"
    | "api-fetch"
    | "template-render"
    | "asset-process"
    | "route-generate"
    | "deploy"

10.2 Build Monitoring

typescript
export class BuildMonitor {
    async trackBuildStart(): Promise<string>
    async trackBuildEnd(buildId: string, result: BuildResult): Promise<void>
    async trackError(buildId: string, error: BuildError): Promise<void>
    async generateBuildReport(buildId: string): Promise<BuildReport>
    
    private logBuildMetrics(result: BuildResult): void
    private notifyOnFailure(error: BuildError): Promise<void>
    }
    
    interface BuildReport {
    buildId: string
    duration: number
    success: boolean
    routesGenerated: number
    assetsProcessed: number
    cacheEfficiency: number
    errors: BuildError[]
    }

11. Summary

This low-level design provides a comprehensive blueprint for implementing the Fusite Web static site generator that:

  • Shares Core Logic: Uses the fusite-commons package to avoid duplication with the API
  • Follows High-Level Route Map: Implements all routes defined in the high-level design (/, /about, /projects, /fuschronicles, etc.)
  • Optimized for Performance: Generates static HTML with efficient asset processing and CDN deployment
  • SEO-Ready: Implements comprehensive metadata, sitemaps, and structured data
  • Maintainable: Clear separation between shared business logic (commons) and web-specific concerns (templates, assets, deployment)

The Web project serves as the public-facing website while the API serves other internal projects, both leveraging the same reliable Notion data access layer.