Fusite High-Level Design

Design Documents
Last updated August 23, 2025

Fusite High-Level Design

1) URL & Route Map (hardcoded)

/                      -> Home (hand-authored template + curated content)
    /about                 -> Static page (code)
    /fuschronicles         -> Listing page with filters & latest posts
    /fuschronicles/{slug}  -> Individual article (from Notion “fuschronicle” DB)
    
    /projects              -> Listing page (public projects only)
    /projects/{slug}       -> Project main page (from Notion “project” DB)
    /projects/{project}/{doc_slug} -> Specific doc for a project (requirements, design, etc.)

All routes are defined in code. Notion provides content and metadata only.


2) Notion Data Model → Site Mapping

A) about me (single page)

  • Content: page blocks
  • Used by: /about

B) project

Properties (required unless marked optional):

  • name (title) → Display name
  • slug (text) → /projects/{slug}
  • description (rich/text) → meta/OG + project card snippet
  • status (select) → “Active”, “Paused”, etc.
  • tags (multi-select) → listing technologies used or other keywords
  • logo (file/url) → shown on cards + OG
  • prod url (url) → external link on project page
  • is_public (checkbox) → filter for listings and sitemap
  • Page content → Project main page body

C) project task (linked to project)

Properties:

  • task (title)
  • status (select)
  • date (date)
  • estimated time (min) (number)
  • actual time (min) (number)
  • link to project (relation → project)
  • Page content → Task description
  • Use cases:
    • /projects/{slug}/backlog (public read-only)

D) fuschronicle

Properties:

  • title (title) → H1 + meta/OG
  • date (date) → publish date + sorting + JSON‑LD
  • type (select: change log | work log | other)
  • projects (relation, multi) → used for /projects/{slug}/timeline, internal links
  • slug (text) → /fuschronicles/{slug} (fallback: yyyy-mm-dd-title)
  • Page content → Article body

Validation rules (build-time):

  • project.is_public === true to appear under /projects + sitemap.
  • fuschronicle.date required
  • Ensure slugs are stable and unique; maintain a redirect map if a slug changes.

3) Build Pipeline (Bun)

Repo layout

/site
    bunfig.toml
    /src
    /notion
    client.ts         # SDK init + helpers
    project.ts        # getters for project DB
    task.ts           # getters for project task DB
    chronicle.ts      # getters for fuschronicle DB
    /render
    templates.ts      # HTML templates (home, listing, project, chronicle)
    seo.ts            # title/meta/canonical/OG/JSON-LD helpers
    routes.ts         # hardcoded route table + slug helpers
    images.ts         # rehost/optimize (to S3)
    /build
    build-all.ts      # full site build
    build-one.ts      # build one route by slug & type
    sitemap.ts        # sitemap.xml generation
    robots.ts         # robots.txt
    feed.ts           # optional RSS for fuschronicles
    deploy.ts         # S3 upload + (optional) CF invalidation
    /build                 # output (gitignored)
    

Steps (full build)

  1. Load config (env: S3 bucket, CF dist, Notion secrets, site URL).
  2. Fetch data:
    • about me: one page.
    • project: all rows (filter is_public).
    • fuschronicle: all rows (no draft concept—use a Published type or date presence).
    • project task: by project (only if backlog page enabled).
  3. Normalize:
    • Compute slugs (prefer notion prop; else break build and throw error).
    • Rehost images to s3://your-site/assets/... and rewrite URLs.
  4. Render pages:
    • Static routes: /, /about, /projects, /fuschronicles
    • Dynamic routes:
      • /projects/{slug}
      • /projects/{slug}/timeline (chronicles filtered by relation)
      • /projects/{slug}/backlog (optional, from project task)
      • /fuschronicles/{slug}
  5. Generate:
    • sitemap.xml (only public pages)
    • robots.txt
  6. Write to S3:
    • index.html, /projects/*/index.html, etc.
  7. Invalidate:
    • Invalidate CloudFront cache

Single‑page regeneration (webhook path)

  • Input: { kind: "project"|"chronicle"|"task"|"about", id: notionId }
  • Lookup: resolve slug & dependencies:
    • If project changed → rebuild:
      • /projects/{slug}, listing /projects, /sitemap.xml, /feed.xml (if needed)
      • If “timeline/backlog” exist → rebuild those too
    • If chronicle changed → rebuild its page, /fuschronicles listing, any related project /timeline
    • If task changed → rebuild that project page + /backlog, plus possibly /projects (if list shows metrics)
    • If about → rebuild /about (and maybe home if it surfaces “about” content)
  • After writing outputs to S3, optionally invalidate the changed paths only.

4) Templates & Page Contracts

Common head (all pages)

  • <title> (≤60 chars), <meta name="description"> (≤160 chars)
  • <link rel="canonical" href="https://fuscripts.com/...">
  • Open Graph/Twitter (title, description, og:image pointing to rehosted image)
  • Structured data:
    • Home: WebSite
    • About: Person
    • Project: CreativeWork (with url, image, inLanguage, about, keywords)
    • Chronicle: BlogPosting (with datePublished, dateModified, author)
  • Preload critical CSS (inline small critical subset; rest defer).

Listings

  • /projects: grid of public projects (logo, name, short description, tags, status), filters by tag/status.
  • /fuschronicles: grouped by type; pagination by date; quick filters for change logs, work logs, other.

Project page

  • Hero: logo, name, tags, status, prod URL.
  • Body: Notion content rendered to HTML.
  • “Related posts”: latest fuschronicle entries linked to this project.
  • “Metrics” (if tasks exist): total tasks, total actual time, first/last date, etc.
  • Links: /timeline, /backlog if enabled.

Chronicle page

  • Title, date, type, related projects chips.
  • Body: Notion content.
  • Prev/next (by date within same type).

5) Data Access (Notion queries)

  • Pagination: pull all records with cursor; store a local JSON cache keyed by Notion page id + last_edited_time to avoid re‑rendering unchanged pages during full builds.
  • Filtering:
    • Projects: is_public == true
    • Chronicles: ensure date exists; type ∈ allowed set
    • Project tasks: fetch by project relation when needed
  • Rate limits: Apply minimal concurrency.

6) Webhook & Regeneration Wiring

  • API Gateway (HTTP API)Lambda (Node):
    • Validate secret/signature.
    • Translate incoming Notion event to {kind, notionId}.
    • Option A: enqueue SQS {kind, notionId} and let a Bun worker Lambda (container) run bun run src/build-one.ts --kind=... --id=....
    • Option B: call the worker Lambda directly (no SQS) if volume is tiny.
  • build-one.ts determines the impact set and renders only those pages + derived listings/sitemap/feed that changed.

7) Hosting & Caching

S3 object headers

  • HTML (.html):
    • Content-Type: text/html; charset=utf-8
    • Cache-Control: public, s-maxage=300, stale-while-revalidate=86400
  • Fingerprinted CSS/JS/images:
    • Cache-Control: public, max-age=31536000, immutable

CloudFront

  • Origin: S3 bucket (not website endpoint)
  • Default root: index.html
  • Custom error pages: 404 → /404.html
  • Optional path invalidations on regen for sub‑minute freshness; otherwise rely on short TTLs.

8) Sitemap, Robots, Feed

  • sitemap.xml:

    • Include: /, /about, /projects, each /projects/{slug}, /fuschronicles, each /fuschronicles/{slug}, and optionally /projects/{slug}/timeline & /backlog if public.
    • lastmod from source last_edited_time (or render time).
  • robots.txt:

    User-agent: *
        Allow: /
        Sitemap: https://fuscripts.com/sitemap.xml
        
  • feed.xml (optional): most recent fuschronicle entries.


9) Assets & OG Images

  • Rehost all Notion images to s3://your-site/assets/... (avoid expiring URLs).
  • Optional: auto-generate OG cards per page (Bun + Satori or HTML→PNG renderer); write to s3://your-site/og/{path}.png and reference in OG/Twitter tags.

Implementation Notes (Bun)

  • Use AWS SDK v3 (@aws-sdk/client-s3, @aws-sdk/client-cloudfront) and Notion SDK (@notionhq/client).
  • Prefer pure ESM; Bun’s fetch is great for image downloads.

“What regenerates when X changes?”

  • Project row: its page, /projects listing, /sitemap.xml, /feed.xml (if project posts in feed), plus /timeline & /backlog if enabled.
  • Project task: the parent project page (metrics), /backlog, /projects (if card shows metrics).
  • Chronicle entry: its page, /fuschronicles listing, related project /timeline, /sitemap.xml, /feed.xml.
  • About page: /about (and / if it surfaces about content).