Showcase Track Guide
Showcase Track - Implementation Path
Section titled “Showcase Track - Implementation Path”Demonstrate technical excellence and best practices
Overview
Section titled “Overview”This track focuses on building a comprehensive showcase project with advanced patterns, comprehensive testing, and professional polish.
Track Philosophy
Section titled “Track Philosophy”- Technical Excellence: Demonstrate best practices and advanced patterns
- Production Ready: Build with enterprise-grade quality
- Comprehensive Testing: Automated quality assurance
- Performance Focused: 98+ Lighthouse scores across all categories
- Developer Experience: TypeScript-first with comprehensive tooling
Best For
Section titled “Best For”- Technical portfolios
- Developer showcases
- Team references
- Client presentations
- Skill demonstrations
- Best practices examples
Success Metrics
Section titled “Success Metrics”| Metric | Target | Why It Matters |
|---|---|---|
| Lighthouse Performance | 98+ | Perfect technical execution |
| Lighthouse Accessibility | 100 | WCAG AA compliance |
| Bundle Size | <160KB JS | Performance budget |
| Test Coverage | 80%+ | Quality assurance |
| TypeScript Coverage | 100% | Type safety |
| Build Time | <60s | Developer productivity |
Phase Implementation Guide
Section titled “Phase Implementation Guide”Phase 0: Foundation (1 day)
Section titled “Phase 0: Foundation (1 day)”💡 Starter Template: Advanced foundation is pre-configured with best practices
Showcase Decisions:
Framework: Astro + Islands ArchitectureStyling: Design Tokens + Tailwind CSSJavaScript: Selective (performance-budgeted)Testing: Vitest + Playwright E2ECI/CD: GitHub ActionsDeployment: GitHub Pages (ships with deploy.yml; Cloudflare Pages optional)Monitoring: Real User Monitoring (RUM)Content: Type-safe Collections + CMS📖 See Also: Islands Architecture Guide for implementation details
Phase 1: Content Architecture (2 days)
Section titled “Phase 1: Content Architecture (2 days)”Showcase Content Collections:
// src/content.config.ts (at the src root — the legacy src/content/config.ts// location and `type: 'content'` are gone with the Content Layer API)import { defineCollection } from 'astro:content';import { glob } from 'astro/loaders';import { z } from 'astro/zod';
const projects = defineCollection({ loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/projects' }), schema: ({ image }) => z.object({ title: z.string(), description: z.string(), technologies: z.array(z.string()), date: z.date(), featured: z.boolean().default(false), cover: image(), gallery: z.array(image()).optional(), demo: z.string().url().optional(), github: z.string().url().optional(), status: z.enum(['completed', 'in-progress', 'planned']).default('completed'), })});
const blog = defineCollection({ loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }), schema: ({ image }) => z.object({ title: z.string(), description: z.string(), date: z.date(), updated: z.date().optional(), author: z.string().default('Your Name'), tags: z.array(z.string()), cover: image().optional(), draft: z.boolean().default(false), featured: z.boolean().default(false), })});
export const collections = { projects, blog };Phase 2: Design System (3 days)
Section titled “Phase 2: Design System (3 days)”💡 Starter Template: Design tokens system is pre-built in
/tokens/
Showcase Design Tokens:
// tokens/base.json (abridged) — raw scales as HSL channel triplets// (ADR-047), never hex{ "color": { "slate": { "50": { "value": "228 22% 98%" }, "500": { "value": "228 12% 52%" }, "900": { "value": "228 20% 13%" } }, "violet": { "500": { "value": "256 86% 63%" } } }, "fontSize": { "sm": { "value": "0.875rem" }, "base": { "value": "1rem" }, "lg": { "value": "1.125rem" } }, "spacing": { "2": { "value": "0.5rem" }, "4": { "value": "1rem" }, "8": { "value": "2rem" } }}Semantic role tokens live in a separate tokens/semantic.json, aliasing the base scales (with optional dark-mode values):
// tokens/semantic.json (abridged){ "semantic": { "primary": { "500": { "value": "{color.violet.500}" } }, "background": { "value": "{color.slate.50}", "dark": "{color.spaceCadet}" }, "foreground": { "value": "{color.charcoal}", "dark": "{color.slate.50}" } }}Phase 3: Tooling
Section titled “Phase 3: Tooling”Showcase CI/CD Pipeline (abridged from the shipped workflow):
name: CI
on: push: branches: [master, develop] pull_request: branches: [master, develop]
jobs: build-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - uses: actions/setup-node@v7 with: node-version-file: '.nvmrc' - uses: pnpm/action-setup@v6
- name: Install dependencies run: pnpm install --frozen-lockfile
- name: Lint, format & type-check run: pnpm run quality:ci
- name: Unit tests with coverage run: pnpm run test:coverage
- name: Validate budget overrides run: pnpm run budgets:validate
- name: Validate semantic color contrast run: pnpm run design:validate
- name: Build site run: pnpm run build
# Followed by the shipped gates: an inline 160KB raw JS size check, # per-image size budgets on source and dist (pnpm run images:gate), # and the font preload budget (pnpm run fonts:gate)
- name: Run E2E tests (Chromium) run: pnpm exec playwright test --project=chromiumLighthouse runs in a separate workflow (.github/workflows/lighthouse.yml) via pnpm exec lhci autorun --config=lighthouserc.json, plus a second mobile pass with lighthouserc.mobile.json — not a marketplace action. pnpm run bundle:analyze is a local analysis script, not a CI step.
Phase 4: Skeleton
Section titled “Phase 4: Skeleton”Showcase Layout System:
The starter’s shipped BaseLayout.astro already covers this — SEO (canonical, Open Graph, Twitter cards, JSON-LD) is built into the <Head /> component per ADR-029, so there is no astro-seo dependency, and view transitions use <ClientRouter /> (never the removed ViewTransitions):
---import { ClientRouter } from 'astro:transitions';import Head from '@/components/molecules/Head.astro';import SkipLink from '@/components/a11y/SkipLink.astro';import Header from '@/components/structural/Header.astro';import Footer from '@/components/structural/Footer.astro';
export interface Props { title: string; description: string; image?: string; canonicalUrl?: URL; noindex?: boolean; ogType?: 'website' | 'article'; ogArticle?: { publishedTime?: string; modifiedTime?: string; author?: string; tags?: string[]; };}---
<!doctype html><html lang="en"> <head> <!-- SEO, favicons, and social tags all live in <Head /> (ADR-029) --> <Head {...Astro.props} /> <ClientRouter /> <!-- Fonts are handled by Astro's Fonts API — preload tags and metric-adjusted fallbacks are generated automatically --> </head> <body> <SkipLink /> <Header /> <main id="main-content" tabindex="-1"> <slot /> </main> <Footer /> </body></html>Phase 5: Components (4 days)
Section titled “Phase 5: Components (4 days)”Showcase Component Library:
-
Button (shipped)
The starter’s Button renders an
<a>whenhrefis set, a<button>otherwise, with token-backed variant and size styles. Extra attributes (id,aria-*, data attributes) pass through via the rest spread; there is nodangervariant and noloadingprop — add those in your own fork if the project needs them:---// src/components/atoms/Button.astro (shipped API)interface Props {variant?: 'primary' | 'secondary' | 'ghost';size?: 'sm' | 'md' | 'lg';class?: string;href?: string;disabled?: boolean;[key: string]: any; // extra attrs pass through}const {variant = 'primary',size = 'md',class: className,href,disabled = false,...rest} = Astro.props as Props;// Token-backed style helpers elided — see the shipped fileconst classList = [baseStyles, getVariantStyles(variant), getSizeStyles(size), className];---{href ? (<a class:list={classList} href={href} aria-disabled={disabled} {...rest}><slot /></a>) : (<button class:list={classList} type="button" disabled={disabled} {...rest}><slot /></button>)} -
Interactive Card Component
Note: the shipped
src/components/molecules/Card.astrois a slot wrapper with props{ class?, animated? }— a richer card like this is your own component:---// e.g. src/components/molecules/RichCard.astro (custom — not the shipped Card.astro)import { Image } from 'astro:assets';import type { ImageMetadata } from 'astro:assets';export interface Props {title: string;description: string;image?: ImageMetadata;href?: string;tags?: string[];date?: Date;interactive?: boolean;}const {title,description,image,href,tags = [],date,interactive = false,} = Astro.props;const CardContent = () => (<>{image && (<div class="card-image"><Imagesrc={image}alt=""widths={[400, 800]}sizes="(max-width: 768px) 100vw, 400px"loading="lazy"/></div>)}<div class="card-body"><h3 class="card-title">{title}</h3><p class="card-description">{description}</p>{date && <time class="card-date">{date.toLocaleDateString()}</time>}<div class="card-tags">{tags.map(tag => <span class="card-tag">{tag}</span>)}</div></div></>);---{interactive ? (<articleclass="card interactive-card"data-href={href}client:visible><CardContent /></article>) : href ? (<a href={href} class="card card-link"><CardContent /></a>) : (<article class="card"><CardContent /></article>)}<script>// Progressive enhancement for interactive cardsdocument.querySelectorAll('.interactive-card').forEach(card => {card.addEventListener('click', (e) => {const href = card.dataset.href;if (href && !e.target.closest('a')) {window.location.href = href;}});});</script> -
Hero Section with Particle Animation
---import Button from '@/components/atoms/Button.astro';import ParticleField from '@/components/islands/ParticleField';
export interface Props { title: string; subtitle?: string; cta?: { text: string; href: string; }; pattern?: 'dots' | 'grid' | 'waves';}
const { title, subtitle, cta, pattern = 'dots' } = Astro.props;---
<section class="hero"> <div class="hero-background"> <ParticleField pattern={pattern} client:idle /> </div>
<div class="hero-content"> <h1 class="hero-title"> {title.split(' ').map((word, i) => ( <span style={{ animationDelay: `${i * 100}ms` }} class="animate-fade-in-up" > {word} </span> ))} </h1>
{subtitle && ( <p class="hero-subtitle">{subtitle}</p> )}
{cta && ( <div class="hero-cta"> <Button href={cta.href} size="lg"> {cta.text} </Button> </div> )} </div></section>
<style> .hero { @apply relative min-h-screen flex items-center justify-center; @apply overflow-hidden; }
.hero-background { @apply absolute inset-0 -z-10; }
.hero-content { @apply relative z-10 text-center px-4; }
.hero-title { @apply text-5xl md:text-7xl font-bold mb-4; @apply bg-clip-text text-transparent bg-gradient-to-r from-primary-500 to-secondary-500; }
.hero-subtitle { @apply text-lg md:text-xl text-foreground-muted max-w-2xl mx-auto mb-8; }
.hero-cta { @apply mt-8; }</style>Phase 6: Sections (2 days)
Section titled “Phase 6: Sections (2 days)”Showcase Content Sections:
- Project Showcase with Filtering
---import { getCollection } from 'astro:content';import ProjectCard from '@/components/molecules/ProjectCard.astro';import FilterBar from '@/components/islands/FilterBar';
const projects = await getCollection('projects');const technologies = [...new Set(projects.flatMap(p => p.data.technologies))];---
<section class="project-showcase"> <div class="container"> <h2 class="section-title">Featured Projects</h2>
<FilterBar filters={technologies} client:visible />
<div class="projects-grid" data-projects> {projects.map(project => ( <div data-tags={project.data.technologies.join(',')}> <ProjectCard project={project} /> </div> ))} </div> </div></section>Phase 7: Content (5 days)
Section titled “Phase 7: Content (5 days)”Showcase Content Collections:
---title: "Interactive Design System"technologies: ['Astro', 'Preact', 'Tailwind', 'Style-Dictionary']date: 2024-01-15cover: ./images/design-system-hero.jpggallery: - ./images/tokens-structure.png - ./images/component-library.png - ./images/documentation-site.png---
import Figure from '@/components/mdx/Figure.astro';import Callout from '@/components/mdx/Callout.astro';import CodeDemo from '@/components/mdx/CodeDemo.astro';
# Building a Design System
<Callout type="info"> This project demonstrates a full-featured design system with token automation, component library, and documentation site.</Callout>
Our goal was to create a single source of truth for design that could be consumed by multiple web applications.
<Figure src="./images/tokens-structure.png" caption="Token structure using Style Dictionary"/>
## Key Features
- Automated token pipeline- WCAG AA compliant color palettes- Interactive component examples
<CodeDemo client:visible> <div slot="preview"> <Button>Click Me</Button> </div> <div slot="code"> ```html <button class="btn btn-primary">Click Me</button> ``` </div></CodeDemo>Phase 8: QA (3 days)
Section titled “Phase 8: QA (3 days)”Showcase Testing Implementation:
-
E2E Test Suite
tests/e2e/critical-paths.spec.ts import { test, expect } from '@playwright/test';test.describe('Critical Paths', () => {test('Homepage loads and has correct title', async ({ page }) => {await page.goto('/');await expect(page).toHaveTitle(/Your Site Name/);});test('Can navigate to projects and filter', async ({ page }) => {await page.goto('/projects');await page.click('button[data-filter="Astro"]');await expect(page.locator('[data-tags*="Astro"]')).toBeVisible();await expect(page.locator('[data-tags*="React"]')).not.toBeVisible();});}); -
Visual Regression Testing (optional add-on)
The starter ships a Playwright E2E suite (nine page specs in
e2e/) but no visual regression tests. If you want them, Playwright’s built-in screenshot assertions make it a small addition (no Percy or paid service needed):// e2e/visual.spec.ts — not shipped; add it yourself if neededimport { test, expect } from '@playwright/test';test('Button component variants', async ({ page }) => {await page.goto('/showcase/');await expect(page.locator('[data-testid="button-variants"]')).toHaveScreenshot('button-variants.png');}); -
Unit Tests for Utilities
src/utils/__tests__/formatDate.test.ts import { expect, test } from 'vitest';import { formatDate } from '../formatDate';test('formats date correctly', () => {const date = new Date('2024-01-15T00:00:00Z');expect(formatDate(date)).toBe('Jan 15, 2024'); // default format is 'short'expect(formatDate(date, 'full')).toBe('January 15, 2024');});
Phase 9: Performance (2 days)
Section titled “Phase 9: Performance (2 days)”Showcase Performance Optimization:
-
Image Optimization Strategy
The shipped
src/components/atoms/Image.astrowrapsastro:assets. Its props are{ src, alt, class?, format?, quality?, width?, height?, sizes?, widths?, densities?, loading?, decoding?, hasShadow? }— the format defaults to AVIF via theresolveImageFormatutility (one output format per image; there is nofallbackFormatprop), default widths are[320, 640, 1024], and lazy loading uses the standardloading="lazy" | "eager"attribute:---// Usage of the shipped src/components/atoms/Image.astroimport Image from '@/components/atoms/Image.astro';import hero from '@/assets/images/hero.jpg';---<Imagesrc={hero}alt="Project hero"widths={[320, 640, 1024]}sizes="(max-width: 800px) 100vw, 800px"loading="lazy"/> -
Font Loading Strategy
The starter uses Astro’s Fonts API instead of hand-written
@font-facerules or@fontsourcepackages. The.woff2files are vendored insrc/assets/fonts/and registered inastro.config.mjs:// astro.config.mjs — variants nest under `options`; the template// registers Geist the same way (both families ship)import { defineConfig, fontProviders } from 'astro/config';export default defineConfig({fonts: [{provider: fontProviders.local(),name: 'Inter',cssVariable: '--font-inter',fallbacks: ['ui-sans-serif', 'system-ui', 'sans-serif'],optimizedFallbacks: true,options: {variants: [{ weight: '100 900', style: 'normal', src: ['./src/assets/fonts/inter-latin-variable.woff2'] },],},},],});---// src/components/molecules/Head.astro — the Font component emits preload// tags and metric-adjusted fallbacks automaticallyimport { Font } from 'astro:assets';---<Font cssVariable="--font-geist" preload /><Font cssVariable="--font-inter" preload />
Phase 10: Deployment (1 day)
Section titled “Phase 10: Deployment (1 day)”Showcase Deployment & Headers (the shipped public/_headers, abridged):
/* Strict-Transport-Security: max-age=63072000; includeSubDomains; preload X-Frame-Options: DENY X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin Permissions-Policy: geolocation=(), microphone=() Content-Security-Policy: ... # header-based CSP — see ADR-051 Cache-Control: public, max-age=0, must-revalidate
/_astro/* Cache-Control: public, max-age=31536000, immutablePhase 11: Documentation (2 days)
Section titled “Phase 11: Documentation (2 days)”Showcase Documentation Examples:
-
Interactive Code Demos
src/components/mdx/CodeDemo.astro ---// ... logic to handle slots---<div class="code-demo"><div class="preview"><slot name="preview" /></div><div class="code"><slot name="code" /></div></div> -
Video Embed Component
src/components/mdx/Video.astro ---interface Props {src: string;title: string;width?: number;height?: number;}const { src, title, width = 16, height = 9 } = Astro.props;---<div style={`aspect-ratio: ${width}/${height}`}><iframesrc={src}title={title}frameborder="0"allowfullscreenloading="lazy"client:visible/></div>
Phase 12: Post-Launch (1 day)
Section titled “Phase 12: Post-Launch (1 day)”Showcase Monitoring & Analytics:
// src/pages/api/analytics.ts — only with an SSR adapter (not shipped)import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request }) => { const data = await request.json();
// Process analytics data const metrics = { ...data, timestamp: new Date().toISOString(), userAgent: request.headers.get('user-agent'), };
// Send to analytics service await sendToAnalytics(metrics);
// Store for internal dashboard await storeMetrics(metrics);
return new Response(JSON.stringify({ success: true }), { status: 200, headers: { 'Content-Type': 'application/json', }, });};Advanced Patterns
Section titled “Advanced Patterns”1. Progressive Enhancement
Section titled “1. Progressive Enhancement”---// Enhance forms progressively---
<form method="POST" action="/api/contact" data-enhance> <!-- Form works without JS --></form>
<script> // Enhance if JS available document.querySelectorAll('[data-enhance]').forEach(form => { form.addEventListener('submit', async (e) => { e.preventDefault();
const formData = new FormData(form); const response = await fetch(form.action, { method: form.method, body: formData, });
// Handle response with better UX }); });</script>2. Performance Budgets
Section titled “2. Performance Budgets”The starter ships its own bundle analysis script (scripts/src/analyze-bundle.ts) — no analyzer integration to install:
# Analyze the production bundle against the JS budgetpnpm run bundle:analyzeIf you need manual chunking, that stays plain Vite config:
import { defineConfig } from 'astro/config';
export default defineConfig({ vite: { build: { rollupOptions: { output: { manualChunks: { 'preact': ['preact'], 'utils': ['./src/utils/index.ts'], }, }, }, }, },});Showcase vs MVP
Section titled “Showcase vs MVP”When to Choose Showcase
Section titled “When to Choose Showcase”✅ Choose Showcase when:
- Building a technical portfolio
- Demonstrating skills to employers
- Creating a team reference
- Have 4-6 weeks available
- Want comprehensive testing
- Need selective interactivity
❌ Avoid Showcase when:
- Timeline is critical
- Content is the only focus
- Working solo with limited time
- Building a simple site
- Learning Astro basics
Migration Path
Section titled “Migration Path”From MVP to Showcase:
- Add testing infrastructure
- Enhance components gradually
- Introduce islands selectively
- Improve documentation
- Add monitoring
Performance Metrics
Section titled “Performance Metrics”Technical Excellence
Section titled “Technical Excellence”- Lighthouse: 98+ all categories
- Bundle size: <160KB JS
- Test coverage: 80%+
- Zero accessibility violations
- Sub-second load times
Developer Experience
Section titled “Developer Experience”- Type safety throughout
- Comprehensive documentation
- Automated testing
- Visual regression prevention
- Easy onboarding
Business Impact
Section titled “Business Impact”- Improved conversions
- Better engagement metrics
- Lower bounce rates
- Higher satisfaction scores
- Reduced maintenance costs
Design Decisions
Section titled “Design Decisions”Islands Architecture
Section titled “Islands Architecture”We use interactive islands sparingly:
- Filter controls
- Search functionality
- Complex forms
- Data visualizations
Performance Strategy
Section titled “Performance Strategy”- AVIF/WebP images with fallbacks
- Critical CSS inlined
- Fonts preloaded
- JS lazy loaded
Deployment
Section titled “Deployment”- GitHub Pages for hosting (shipped
deploy.yml; Cloudflare Pages optional) - GitHub Actions for CI/CD
- CI gates (perf budgets, Lighthouse floors) catch regressions before merge
Conclusion
Section titled “Conclusion”The Showcase track demonstrates your ability to build production-grade applications with modern best practices. It’s an investment in quality that pays dividends through easier maintenance, better performance, and a portfolio piece that stands out.
Remember: The goal isn’t to use every feature, but to thoughtfully apply advanced patterns where they add value. Show restraint in your technical choices while demonstrating depth in your implementation.