Day 13: Build Case Study Pages
Day 13 was about turning yesterday’s structured case study content into real, polished pages: a listing page, a detail template, and the supporting components, styling, and tests to keep everything reliable and readable.
Update
- Shipped a new case studies listing page at
/case-studies - Shipped statically generated case study detail pages at
/case-studies/[slug] - Built a reusable
CaseStudyCardcomponent with visual accents per project type - Expanded the case study content schema to include
clientName(plus optionalprojectSource) - Hardened URL validation for external links and added dedicated tests
- Added unit tests + fast-check property tests to validate page structure and data consistency
- Updated Lighthouse CI config to include the new case study routes
What Shipped
1. /case-studies Listing Page
The new listing page (src/pages/case-studies/index.astro) loads all case studies via getCaseStudies() and renders them in a responsive grid. It includes:
- A hero section with clear positioning and an intro
- A case study grid powered by the new
CaseStudyCard - A bottom CTA block linking to
/assessmentand/book - Reduced-motion support and keyboard focus styles
2. /case-studies/[slug] Detail Pages
The detail template (src/pages/case-studies/[slug].astro) uses getStaticPaths() to generate one page per case study. Each page includes:
- Breadcrumb-style back navigation (
/case-studies) - A hero block with project title, client type label, and optional metadata (e.g. “collaborating since…”)
- Structured sections for Overview, Scope, Challenge, Constraints & Context, Solution, Technologies, and Outcomes
- Optional testimonial block with attribution
- A CTA section at the end (book / assessment)
All external links coming from content frontmatter are passed through validateUrlOrFallback() to avoid unsafe href values.
3. Case Study Card Component
src/components/case-studies/CaseStudyCard.astro renders the minimum “scan” info needed on the listing page:
- Project title, client type label, short description
- A few technology tags (with a
+Nindicator when there are more) - Accent colors and hover states keyed off
clientType
Accessibility details were treated as first-class: aria-label on the card link, :focus-visible styles, and touch targets ≥ 44px.
4. Content Model Improvements
The case study schema was updated (src/content/config.ts + src/content/caseStudies.ts) to support richer storytelling:
- Added required
clientName - Added optional
projectSource(useful for attribution like referral marketplaces)
The existing case study markdown files were updated to match the schema (src/content/case-studies/*.md).
5. Safer URL Handling + Better Tests
We expanded src/utils/urlValidation.ts so it can be used safely across the site:
- Allows only
http:,https:, andmailto:URLs (plus absolute same-origin paths starting with/) - Rejects
javascript:anddata:protocols - Adds
validateUrlOrFallback()for safe defaults in templates - Tightens
validateBookingUrl()to validate allowed domains
This is covered by a dedicated test suite (src/utils/urlValidation.test.ts).
6. Test Coverage (Unit + Property Tests)
Because astro:content imports aren’t test-friendly, a small helper (src/__tests__/case-studies-test-helper.ts) loads markdown files directly with gray-matter and validates frontmatter via Zod.
On top of that, we added:
- Page source tests for
/case-studiesand/case-studies/[slug] - Component tests for
CaseStudyCard - Fast-check property tests ensuring listing/detail completeness and consistent structure