Skip to content

Showcase Track Guide

Demonstrate technical excellence and best practices

This track focuses on building a comprehensive showcase project with advanced patterns, comprehensive testing, and professional polish.

  • 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
  • Technical portfolios
  • Developer showcases
  • Team references
  • Client presentations
  • Skill demonstrations
  • Best practices examples
MetricTargetWhy It Matters
Lighthouse Performance98+Perfect technical execution
Lighthouse Accessibility100WCAG AA compliance
Bundle Size<160KB JSPerformance budget
Test Coverage80%+Quality assurance
TypeScript Coverage100%Type safety
Build Time<60sDeveloper productivity

💡 Starter Template: Advanced foundation is pre-configured with best practices

Showcase Decisions:

Framework: Astro + Islands Architecture
Styling: Design Tokens + Tailwind CSS
JavaScript: Selective (performance-budgeted)
Testing: Vitest + Playwright E2E
CI/CD: GitHub Actions
Deployment: 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

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 };

💡 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}" }
}
}

Showcase CI/CD Pipeline (abridged from the shipped workflow):

.github/workflows/ci.yml
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=chromium

Lighthouse 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.

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):

src/layouts/BaseLayout.astro
---
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>

Showcase Component Library:

  1. Button (shipped)

    The starter’s Button renders an <a> when href is 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 no danger variant and no loading prop — 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 file
    const 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>
    )}
  2. Interactive Card Component

    Note: the shipped src/components/molecules/Card.astro is 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">
    <Image
    src={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 ? (
    <article
    class="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 cards
    document.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>
  3. Hero Section with Particle Animation

src/components/structural/Hero.astro
---
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>

Showcase Content Sections:

  1. Project Showcase with Filtering
src/components/structural/ProjectShowcase.astro
---
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>

Showcase Content Collections:

src/content/projects/project-one.mdx
---
title: "Interactive Design System"
technologies: ['Astro', 'Preact', 'Tailwind', 'Style-Dictionary']
date: 2024-01-15
cover: ./images/design-system-hero.jpg
gallery:
- ./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>

Showcase Testing Implementation:

  1. 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();
    });
    });
  2. 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 needed
    import { 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');
    });
  3. 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');
    });

Showcase Performance Optimization:

  1. Image Optimization Strategy

    The shipped src/components/atoms/Image.astro wraps astro:assets. Its props are { src, alt, class?, format?, quality?, width?, height?, sizes?, widths?, densities?, loading?, decoding?, hasShadow? } — the format defaults to AVIF via the resolveImageFormat utility (one output format per image; there is no fallbackFormat prop), default widths are [320, 640, 1024], and lazy loading uses the standard loading="lazy" | "eager" attribute:

    ---
    // Usage of the shipped src/components/atoms/Image.astro
    import Image from '@/components/atoms/Image.astro';
    import hero from '@/assets/images/hero.jpg';
    ---
    <Image
    src={hero}
    alt="Project hero"
    widths={[320, 640, 1024]}
    sizes="(max-width: 800px) 100vw, 800px"
    loading="lazy"
    />
  2. Font Loading Strategy

    The starter uses Astro’s Fonts API instead of hand-written @font-face rules or @fontsource packages. The .woff2 files are vendored in src/assets/fonts/ and registered in astro.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 automatically
    import { Font } from 'astro:assets';
    ---
    <Font cssVariable="--font-geist" preload />
    <Font cssVariable="--font-inter" preload />

Showcase Deployment & Headers (the shipped public/_headers, abridged):

public/_headers
/*
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, immutable

Showcase Documentation Examples:

  1. 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>
  2. 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}`}>
    <iframe
    src={src}
    title={title}
    frameborder="0"
    allowfullscreen
    loading="lazy"
    client:visible
    />
    </div>

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',
},
});
};
---
// 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>

The starter ships its own bundle analysis script (scripts/src/analyze-bundle.ts) — no analyzer integration to install:

Terminal window
# Analyze the production bundle against the JS budget
pnpm run bundle:analyze

If you need manual chunking, that stays plain Vite config:

astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
vite: {
build: {
rollupOptions: {
output: {
manualChunks: {
'preact': ['preact'],
'utils': ['./src/utils/index.ts'],
},
},
},
},
},
});

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

From MVP to Showcase:

  1. Add testing infrastructure
  2. Enhance components gradually
  3. Introduce islands selectively
  4. Improve documentation
  5. Add monitoring
  • Lighthouse: 98+ all categories
  • Bundle size: <160KB JS
  • Test coverage: 80%+
  • Zero accessibility violations
  • Sub-second load times
  • Type safety throughout
  • Comprehensive documentation
  • Automated testing
  • Visual regression prevention
  • Easy onboarding
  • Improved conversions
  • Better engagement metrics
  • Lower bounce rates
  • Higher satisfaction scores
  • Reduced maintenance costs

We use interactive islands sparingly:

  • Filter controls
  • Search functionality
  • Complex forms
  • Data visualizations
  • AVIF/WebP images with fallbacks
  • Critical CSS inlined
  • Fonts preloaded
  • JS lazy loaded
  • 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

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.