fast-website
Lightweight, SEO-first website for SanthoshJ that aggregates YouTube + Instagram + podcast content and tracks conversion events via Umami.
Specs (OpenSpec)
This repo uses OpenSpec (schema: spec-driven) to document and ship features.
- Active specs live in
openspec/specs/<spec-name>/spec.md - Completed initiatives (proposal/design/tasks + delta specs) live in
openspec/changes/archive/<date>-<change-name>/
Key public flags (documented in site/.env.example):
PUBLIC_SITE_URL: canonical URL basePUBLIC_ENABLE_SW: set to"false"to disable service worker registrationPUBLIC_ENABLE_NAV_HOVER_LINE: set to"false"to disable decorative hover-line styling
Spec Index (Active)
Content + ingestion:
social-content-aggregation(openspec/specs/social-content-aggregation/spec.md): normalize YouTube/Instagram/podcast items and refresh via cached ingestionhomepage-content-modules(openspec/specs/homepage-content-modules/spec.md): homepage module ordering, newest feed, high-performing videos, and Instagram omission when emptywordpress-content-source(openspec/specs/wordpress-content-source/spec.md): fetch WordPress posts/pages/categories viawp-jsonand write to build cacheblog-section-surface(openspec/specs/blog-section-surface/spec.md):/blogsurface (index/category/detail) + Umami-instrumented navigationseo-content-surface(openspec/specs/seo-content-surface/spec.md): indexable pages, canonical URLs, sitemap + robots, and JSON-LD expectations
Analytics + tracking:
interaction-tracking-taxonomy(openspec/specs/interaction-tracking-taxonomy/spec.md): required Umami event attributes,target_idnamespaces, and modal interaction eventsanalytics-umami(openspec/specs/analytics-umami/spec.md): Umami enable/disable behavior + supported custom eventsconversion-ctas(openspec/specs/conversion-ctas/spec.md): reusable CTA surface + UTM support +cta_clicktracking
UI + UX shell:
wcag-responsive-ui(openspec/specs/wcag-responsive-ui/spec.md): responsive nav shell, focus-visible baseline, reduced motion, and semantic element rulescard-layout-system(openspec/specs/card-layout-system/spec.md): standardized card information architecture + modal-trigger behavior for video/podcast cardsmedia-modal(openspec/specs/media-modal/spec.md):<dialog>-based media preview modal requirements (focus, playback stop, crawlable CTAs)image-lazy-loading(openspec/specs/image-lazy-loading/spec.md): shimmer placeholders + fade-in + failure handling + reduced-motion behaviorsite-theming(openspec/specs/site-theming/spec.md):data-themeapplication, defaults, and persistence rules (localStorage + cookie fallback)theme-switcher-notch(openspec/specs/theme-switcher-notch/spec.md): floating theme notch placement, interaction, and accessibility requirementsnavbar-branding(openspec/specs/navbar-branding/spec.md): header logo + centered brand layoutnav-hover-line(openspec/specs/nav-hover-line/spec.md): decorative hover-line treatment for header titles + key surface titles (flagged)
Performance + deployment:
service-worker-performance(openspec/specs/service-worker-performance/spec.md): production SW registration + runtime caching + safe updatesresponsive-image-delivery(openspec/specs/responsive-image-delivery/spec.md): explicit dimensions + deterministic image behavior for quality gateslighthouse-quality-gate(openspec/specs/lighthouse-quality-gate/spec.md): deterministic Lighthouse runner + 100-score assertiondocker-content-refresh(openspec/specs/docker-content-refresh/spec.md): Docker-only host refresh/update workflow (no Node.js on server)cache-layer(openspec/specs/cache-layer/spec.md): Redis-backed shared cache + TTL + manual cache clear
Quality Gates
- Site build:
npm -C site run build - Lighthouse gate docs:
site/docs/lighthouse.md(scripts:npm -C site run lighthouse:run,npm -C site run verify:lighthouse)
Completed Initiatives (Archived)
Each archived initiative includes proposal.md, design.md, tasks.md, and any delta specs used for that change.
| Change | Focus |
|---|---|
2026-02-10-dynamic-homepage-social-acquisition |
Initial SEO-first site: content aggregation, homepage modules, CTAs, and analytics |
2026-02-10-better-tracking |
Site-wide click tracking taxonomy aligned to Umami |
2026-02-10-custom-events-umami |
Expand Umami custom event coverage + standardize event properties |
2026-02-10-blog-umami-fix |
Fix and verify blog surface Umami instrumentation |
2026-02-10-better-cache |
Add Redis cache layer + TTL + manual clear; wire into ingestion |
2026-02-10-card-layout |
Standardize card layout across surfaces |
2026-02-10-lazy-loading |
Add shimmer placeholders and reduced-motion-safe image loading UX |
2026-02-10-blogs-section |
Add WordPress-backed blog section + routes + secondary nav |
2026-02-10-hide-ig-if-no-data |
Omit Instagram module when dataset is empty |
2026-02-10-reduce-bounce-rate |
Add in-page media modal previews for video/podcast cards |
2026-02-10-service-workers |
Add service worker caching for repeat-visit performance |
2026-02-10-deploy-without-node |
Docker-only host refresh/update workflow |
2026-02-10-fix-sub-pages |
Fix static serving so /videos, /podcast, /about do not 404 |
2026-02-10-wcag-responsive |
WCAG baseline + responsive nav shell + typography/background fixes |
2026-02-10-lighthouse-fixes |
Lighthouse cleanup + deterministic 100/100/100/100 gate |
2026-02-11-dch-theming |
Dark/light/high-contrast themes + theme notch UI |
2026-02-11-remember-theme |
Persist theme across visits + emit theme switch tracking |
2026-02-11-final-touches |
Header branding polish + hover-line treatment behind env flag |
Local Setup
cd site
cp .env.example .env
npm install
npm run fetch-content
npm run dev
Open http://localhost:4321.
Configuration
All configuration is via environment variables (see site/.env.example).
YouTube
YOUTUBE_CHANNEL_IDis required for ingestion.- Optional:
YOUTUBE_API_KEYenables view-count ingestion for high-performing ranking.
Notes:
- The channel ID looks like
UC...and can be found by viewing the channel page source or via YouTube Studio settings. - If
YOUTUBE_API_KEYis not set, ingestion falls back to the public channel RSS feed and metrics will be missing.
Podcast
PODCAST_RSS_URLis required to ingest episodes.
Instagram (Embed-First)
Edit site/content/instagram-posts.json:
{
"posts": [
{ "url": "https://www.instagram.com/p/<id>/", "publishedAt": "2026-02-01T00:00:00Z", "title": "Movie post" }
]
}
Phase 2 (API ingestion) path:
- Requires Meta developer app setup, correct account type, token issuance + refresh, and platform policy compliance.
- Recommended approach: keep embed-first for v1, then add an optional API ingest module when constraints are clear.
Curation / Featured Videos
If YouTube metrics are not available, curate high-performing videos in site/content/featured-videos.json:
{ "videoIds": ["abc123", "def456"] }
Analytics (Umami)
Set:
PUBLIC_UMAMI_SCRIPT_URLPUBLIC_UMAMI_WEBSITE_ID
If either is missing, analytics is disabled and the site still functions.
Event tracking is implemented using Umami's data-umami-event and data-umami-event-* attributes.
Click Tracking Taxonomy (Umami-Compatible)
All tracked clickables MUST set:
data-umami-event: the event name (max 50 chars), e.g.click,cta_click,outbound_clickdata-umami-event-target_id: stable unique identifier for the clickable element (namespaced likenav.*,cta.*,card.*)data-umami-event-placement: where the clickable appears (e.g.nav,hero,section_header,home.newest)
For links, also set:
data-umami-event-target_url: destination URL (can be relative for internal navigation)
Common additional properties:
data-umami-event-domain: destination domain (for outbound links)data-umami-event-source:youtube/instagram/podcastwhere applicabledata-umami-event-ui_placement: a sub-placement for UI grouping (e.g.content_card)
Instrumentation checklist:
- Every clickable should have a unique
target_idso it can be segmented in Umami. - Use categorical ids and placements only (no PII).
Deployment (Linode + Docker)
The production host is intentionally minimal and only needs Docker (no Node.js on the server).
Local Docker
docker compose build
docker compose up -d
The container serves the static output on port 8080 (map or proxy as needed).
Production (Docker-Only Host)
In production, CI builds and publishes a Docker image (nginx serving the static output). The server updates by pulling that image and restarting the service.
Runbook: deploy/runbook.md.
Refreshing Content (Manual, Docker-Only)
Content is fetched at build time into site/content/cache/content.json (typically in CI), then packaged into the image.
On the server host:
./scripts/refresh.sh
This:
- Pulls the latest published image
- Restarts the service (no build on the host)
Refreshing Content (Scheduled)
Install a daily cron using deploy/cron.example as a starting point.
Rollback:
- Re-deploy a known-good image tag/digest (see
deploy/runbook.md).