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.
- Designer & full-stack developer
- 2026
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
- Next.js 16 (App Router)
- React 19
- TypeScript
- Tailwind CSS 4
- Semantic surface tokens
- Geist Sans & Mono
- Typed content modules
- JSON-LD
- Generated OG images
- Contact API with Resend or Formspree
- Vercel
- 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.