Skip to content
Joey Landry

LivePersonal site · Engineering

joeylandry.org

A personal site treated as a small product: typed content, a deliberate design system, honest imagery and a test suite, so it stays accurate as the work it describes keeps changing.

Role
Designer & full-stack developer
Year
2026
Illustrated cover for joeylandry.org: a browser window containing a smaller copy of the same browser window, nested again and again toward a vanishing point, in gold on midnight ink.
Illustrated cover — not a screenshot

Why it exists

A portfolio is usually the least maintained thing an engineer owns. It gets written once, drifts out of date, and quietly starts describing someone who no longer exists.

I wanted one that behaves like the rest of my work: every fact stored once, every page generated from it, and a build that fails when something stops being true.

What I owned

  • Visual direction, typography and the two-surface design system.
  • The typed content layer and every word of copy in it.
  • Routing, metadata, structured data, sitemap and generated social images.
  • The contact endpoint: validation, spam resistance and rate limiting.
  • The authored project covers and the script that generates them.
  • Unit and end-to-end tests, including accessibility and contrast checks.

Product & UX calls

Copy is data, not markup

Every sentence lives in typed modules under content/. Components never hardcode copy, so cards, case studies, the sitemap and structured data all read the same source and cannot disagree with each other.

Illustrations that admit they are illustrations

Project covers are authored artwork rather than screenshots, and the interface says so in a caption. A fabricated product capture would be a small lie on the first thing people look at.

Complete without JavaScript

Scroll reveals are opt-in: content is visible by default and only animates once a script has confirmed it can. With scripts disabled or reduced motion requested, the page is simply finished.

How it's built

Application

Next.js 16 App Router with React 19 and TypeScript. Every page except the contact endpoint is statically prerendered, with per-project routes generated from the content layer.

Design system

Two surfaces, midnight ink and warm paper, expressed as semantic custom properties declared through Tailwind 4 utilities. One class on a section swaps its entire colour context, and each project accent resolves to an AA-safe value on either surface.

Metadata

Canonical URLs, JSON-LD, the sitemap and per-route Open Graph images are derived from the same project data the pages render, so adding a project updates all of them.

Contact

A server route validates independently of the client, carries a honeypot and minimum fill time, applies a per-IP rate limit, and reports honestly when no delivery provider is configured.

Everything in the build

Framework
  • Next.js 16 (App Router)
  • React 19
  • TypeScript
Interface
  • Tailwind CSS 4
  • Semantic surface tokens
  • Geist Sans & Mono
Content
  • Typed content modules
  • JSON-LD
  • Generated OG images
Platform
  • Contact API with Resend or Formspree
  • Vercel
Quality
  • Vitest
  • Playwright
  • Automated contrast checks

The hard parts

Base case

Describing a site from inside that same site is, strictly speaking, recursion. The hard part was making sure it terminates.

Where it landed

  • The site you are reading: statically generated, accessible, and driven entirely by typed content.
  • Tests assert content integrity, heading order, keyboard operation, focus visibility and WCAG AA contrast on every route.
  • Adding or retiring a project is a single content change; routes, metadata and navigation follow.

What I'd explore next

Directions I'm considering — none of this is built yet.

  • Real product captures to sit alongside the authored covers.
  • Short write-ups on individual engineering decisions, published from the same content layer.