Skip to content
← Back to projects

JordanThirkle.com

A production-grade portfolio and blog built with Astro 6, React 19, and Tailwind CSS. Engineered for 100 Lighthouse scores, WCAG 2.2 AA compliance, and zero-JS baseline with interactive React islands.

Tech Stack

Astro 6, React 19, TypeScript, Tailwind CSS, Nanostores, Playwright, Sharp, Vercel

Timeline

Continuous

Category

Websites

Mission

Build the fastest, most accessible developer portfolio on the web. Zero-JS baseline, 100 Lighthouse scores, fully theme-aware, and AI-accelerated development workflows.

JordanThirkle.com

Overview

It serves as my portfolio and blog. Built with Astro’s Islands Architecture, it ships 0kb of JavaScript for static articles, only hydrating the interactive command palette and engagement dock as React components.

Technical Deep Dive

Islands Architecture

The primary challenge was keeping the site fast and responsive while maintaining interactivity where it matters. Using Astro, only the CommandPalette and EngagementDock are hydrated as React components. Everything else is static HTML/CSS, resulting in fast page transitions and strong Lighthouse scores.

The React islands use client:idle hydration — they load after the main page content is fully interactive. This keeps the critical rendering path clean:

  • First Contentful Paint: ~0.6s (static HTML from pre-rendered pages)
  • Largest Contentful Paint: ~0.9s (hero image or heading text)
  • Time to Interactive: ~1.4s (including React hydration for islands)
  • Total Blocking Time: 0ms (no main-thread blocking scripts)

View Transitions

Astro’s view transition API powers smooth page navigations. The <main> element uses transition:animate="slide" for horizontal slide transitions, while <nav> and <footer> use transition:persist to avoid re-rendering shared elements across navigations.

The theme toggle state is maintained across transitions via an astro:after-swap event listener that re-reads localStorage.theme and re-applies the data-theme attribute.

Performance Budget

Every build is measured against strict thresholds enforced by Lighthouse CI:

MetricThresholdCurrent
Performance≥ 9096–100
Accessibility≥ 9597–100
SEO≥ 95100
LCP≤ 2500ms~900ms
CLS≤ 0.1~0.005
TBT≤ 200ms0ms

Image Pipeline

Images go through a two-stage pipeline:

  1. Build-time generation — OG images are generated via Satori (JSX-to-SVG-to-PNG) for every blog post and project
  2. Post-build optimisation — A Sharp script (scripts/optimize-images.mjs) scans all PNGs in the output and generates WebP + AVIF variants with quality tuning (WebP: 80, AVIF: 65)

The pipeline produces ~74 optimised images per build from ~55 source images, saving approximately 2.4 MB.

Custom Search & Filtering

Instead of reaching for a heavy search library like Algolia for a small site, I built a custom search index using Astro’s content collections. The CommandPalette performs weighted searches across titles, tags, and descriptions, providing a Spotlight-style experience without external API calls.

Theme Engine

The theme system uses a lightweight nanostore (themeStore) that syncs with both localStorage and the data-theme HTML attribute. The store is initialised synchronously in an inline <script> block before any paint to prevent flash of unstyled content (FOUC).

All color values are CSS variable tokens — no hex codes exist in component templates. The token system provides:

  • Text tokens: text-primary (headings), text-secondary (body), text-tertiary (metadata), text-quaternary (captions)
  • Surface tokens: bg-page (page background), bg-surface (card backgrounds), bg-surface-elevated (hovered cards)
  • Border tokens: border-border (default), border-border-strong (active/focused)
  • Accent tokens: text-accent-primary (links), text-accent-secondary (tags)

All tokens are defined in global.css under [data-theme="dark"] and [data-theme="light"] selector blocks.

Newsletter System

The /api/subscribe serverless function integrates with Buttondown for email delivery. It includes:

  • Honeypot anti-spam: Hidden firstName field — bots that fill it get a silent 200 response (no subscription)
  • Rate limiting: In-memory token bucket (5 requests per 60 seconds per IP)
  • Origin check: Validates the request origin matches the deployed site
  • Graceful error handling: Returns descriptive errors for invalid emails, rate limits, and upstream failures

Changelog System

Projects support a changelog frontmatter field — an array of { date, type, description } objects with types feature | fix | perf | design | content. The Changelog.astro component renders these as a styled timeline on each project detail page, always in reverse chronological order.

Accessibility

Every page is tested against WCAG 2.2 AA standards via axe-core Playwright integration. The test suite covers 11 routes (home, about, blog, blog post, projects, project detail, contact, newsletter, uses, services, 404) with zero violations enforced in CI.

Specific implementations:

  • Touch targets: All interactive controls use the tap-target class (44×44px minimum, WCAG 2.5.5)
  • Focus indicators: Visible :focus-visible outlines on all interactive elements
  • ARIA: Semantic landmarks, descriptive labels, live regions for dynamic content
  • Reduced motion: All scroll reveals and view transitions respect prefers-reduced-motion

Connected Guides

If you want to see the specific decisions behind this build, check out these deep dives:

The Takeaway

Creator Hub shows that modern static-first frameworks like Astro let you build sites that are both visually polished and extremely fast — without reaching for a heavy SPA framework. The key decisions (zero-JS baseline, semantic tokens, CI-enforced quality gates) all serve the same goal: a site that loads instantly, works for everyone, and stays maintainable as it grows.

Changelog

Feature

Added Lighthouse CI, scroll-triggered reveals, and Astro view transitions for smooth page navigation.

Performance

Added Sharp build-time image optimization — 74 WebP/AVIF files generated, ~2.3 MB saved.

Performance

Added web-vitals RUM tracking via Plausible custom events (LCP, CLS, INP).

Performance

Non-blocking Google Fonts load with media-print swap pattern. Added Inter fallback with size-adjust to prevent CLS.

Design

Transparent logo across favicon, navbar, and footer. Theme-aware SVG favicon with prefers-color-scheme support.

Fix

Fixed all 40+ audit issues — double-branded titles, contrast ratios, missing ARIA, form labels, memory leaks, API security.

Feature

Added XML sitemap filter for noindex pages. Added SoftwareApplication + WebSite JSON-LD schemas.

Fix

Fixed blog card click target — absolute inset-0 overlay was missing position: relative on parent article.

Fix

Fixed blog index — duplicate URL sync, timeline data-date, redundant RSS link, dead-code image fallback.

Feature

Added category filter + grid/timeline view toggle to blog and project indexes.

Design

Theme persistence engine — semantic color tokens, Framer Motion toggle, localStorage sync across view transitions.

Performance

Achieved 100/100 Lighthouse scores across all pages. Fixed contrast, search hardening, navigation anchors.

Feature

Command palette with keyboard navigation. Toast notification system. Newsletter API with honeypot anti-spam.

Feature

Engagement dock (likes, views, share) with local-first storage. Giscus blog comments integration.

Feature

Initial blog posts and MDX content collections with Zod validation. OG image generation pipeline via Satori.

Feature

Initial site launch — Astro 6, React 19, Tailwind CSS, Vercel deployment, blog + projects + about pages.

0