Phase 4 - Code Examples
Code Examples
Section titled “Code Examples”Base Layout Component
Section titled “Base Layout Component”***
import { ViewTransitions } from 'astro:transitions';import Header from '@/components/layout/Header.astro';import Footer from '@/components/layout/Footer.astro';import SkipLink from '@/components/a11y/SkipLink.astro';import ThemeSetup from '@/components/ThemeSetup.astro'; // ADDED: Import for theme setup componentimport '@fontsource-variable/inter';import '@/styles/global.css';
export interface Props { title: string; description: string; image?: string; canonicalURL?: URL; noindex?: boolean;}
const { title, description, image = '/og-default.jpg', canonicalURL = new URL(Astro.url.pathname, Astro.site), noindex = false,} = Astro.props;
const siteTitle = 'Your Site Name';const fullTitle = title === siteTitle ? title : `${title} | ${siteTitle}`;
***
<!doctype html><html lang="en" class="scroll-smooth"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="generator" content={Astro.generator} />
<!-- Favicons --> <link rel="icon" type="image/svg+xml" href="./favicon.svg" /> <link rel="icon" type="image/x-icon" href="./favicon.ico" /> <link rel="apple-touch-icon" sizes="180x180" href="./apple-touch-icon.png" />
<!-- Primary Meta Tags --> <title>{fullTitle}</title> <meta name="title" content={fullTitle} /> <meta name="description" content={description} /> <link rel="canonical" href={canonicalURL} />
{noindex && <meta name="robots" content="noindex, nofollow" />}
<!-- Open Graph --> <meta property="og:type" content="website" /> <meta property="og:url" content={canonicalURL} /> <meta property="og:title" content={fullTitle} /> <meta property="og:description" content={description} /> <meta property="og:image" content={new URL(image, Astro.site)} /> <meta property="og:site_name" content={siteTitle} />
<!-- Twitter --> <meta property="twitter:card" content="summary_large_image" /> <meta property="twitter:url" content={canonicalURL} /> <meta property="twitter:title" content={fullTitle} /> <meta property="twitter:description" content={description} /> <meta property="twitter:image" content={new URL(image, Astro.site)} />
<!-- Font Preloading --> <link rel="preload" href="./_astro/inter-latin-400-normal.woff2" as="font" type="font/woff2" crossorigin="anonymous" /> <link rel="preload" href="./_astro/inter-latin-700-normal.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
{/* Theme Detection & Setup - REPLACED inline script with a component */} {/* The theme detection script, previously an inline script, is now encapsulated within the 'ThemeSetup.astro' component. This component's script runs on the client to apply the theme early in the rendering process. Astro processes this script, allowing for CSP compliance.
Example for src/components/ThemeSetup.astro: --- // No server-side logic needed for this simple script. // Astro will extract and bundle the script tag below. --- <script> const theme = (() => { if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) { return localStorage.getItem('theme'); } if (window.matchMedia('(prefers-color-scheme: dark)').matches) { return 'dark'; } return 'light'; })(); if (theme === 'dark') { document.documentElement.classList.add('dark'); } else { document.documentElement.classList.remove('dark'); } </script> */} <ThemeSetup client:load /> {/* client:load ensures it runs ASAP */}
<ViewTransitions /> </head> <body class="flex min-h-screen flex-col bg-background text-foreground antialiased"> <SkipLink /> <Header /> <main id="main-content" class="flex-1"> <slot /> </main> <Footer /> </body></html>Header Component
Section titled “Header Component”***
import { getCollection } from 'astro:content';import ThemeToggle from '@/components/ThemeToggle.astro';import MobileMenu from '@/components/MobileMenu.astro';
const navigation = await getCollection('navigation');const navItems = navigation[0]?.data.items || [];
***
<header class="sticky top-0 z-50 w-full border-b border-border bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60"> <div class="container flex h-16 items-center justify-between"> <a href="./" class="flex items-center space-x-2" aria-label="Home"> <svg class="h-8 w-8 text-primary-600 dark:text-primary-400" viewBox="0 0 32 32" fill="currentColor" aria-hidden="true" > <!-- Your logo SVG --> <rect x="4" y="4" width="24" height="24" rx="4" /> </svg> <span class="font-bold text-xl">Your Name</span> </a>
<nav class="hidden md:flex items-center space-x-6" aria-label="Main navigation"> {navItems.map((item) => ( <a href={item.href} class="text-sm font-medium text-foreground/60 transition-colors hover:text-foreground focus-visible-ring" aria-current={Astro.url.pathname === item.href ? 'page' : undefined} > {item.label} </a> ))} </nav>
<div class="flex items-center space-x-4"> <ThemeToggle /> <MobileMenu items={navItems} /> </div> </div></header>Mobile Navigation Component
Section titled “Mobile Navigation Component”***
// This Astro component now delegates its interactive parts to a client-side island.import MobileMenuIsland from '@/components/islands/MobileMenuIsland.tsx'; // Or .jsx, .vue, .svelte
export interface Props { items: Array<{ label: string; href: string; isExternal?: boolean; }>;}
const { items } = Astro.props;
***
{/* The visual structure and interactive logic for the mobile menu are now handled by the 'MobileMenuIsland' client component. This approach adheres to the 'inline_scripts: 0' budget rule by moving JavaScript into Astro-processed client components.
The island (e.g., src/components/islands/MobileMenuIsland.tsx) would receive 'items' as props and internally manage its state, event listeners (for button clicks, escape key, etc.), ARIA attributes, and rendering of the menu button and panel.
The <script> tag and detailed HTML previously described here for direct implementation have been removed in favor of this island-based approach for better CSP compliance and componentization.*/}<MobileMenuIsland items={items} client:visible />{/* client:visible or client:idle is appropriate for a menu not critical at load time */}Footer Component
Section titled “Footer Component”***
const currentYear = new Date().getFullYear();
const footerLinks = [ { label: 'Privacy', href: '/privacy' }, { label: 'Terms', href: '/terms' }, { label: 'RSS', href: '/rss.xml' },];
const socialLinks = [ { label: 'GitHub', href: 'https://github.com/yourusername', icon: 'github' }, { label: 'LinkedIn', href: 'https://linkedin.com/in/yourusername', icon: 'linkedin' }, { label: 'Twitter', href: 'https://twitter.com/yourusername', icon: 'twitter' },];
***
<footer class="border-t border-border bg-background"> <div class="container py-8"> <div class="grid gap-8 md:grid-cols-3"> <!-- About --> <div> <h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-foreground/60"> About </h2> <p class="text-sm text-foreground/60"> Creating beautiful, performant web experiences with modern technologies. </p> </div>
<!-- Quick Links --> <div> <h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-foreground/60"> Links </h2> <ul class="space-y-2"> {footerLinks.map((link) => ( <li> <a href={link.href} class="text-sm text-foreground/60 hover:text-foreground focus-visible-ring inline-block" > {link.label} </a> </li> ))} </ul> </div>
<!-- Social --> <div> <h2 class="mb-3 text-sm font-semibold uppercase tracking-wider text-foreground/60"> Connect </h2> <div class="flex space-x-4"> {socialLinks.map((link) => ( <a href={link.href} class="text-foreground/60 hover:text-foreground focus-visible-ring" aria-label={link.label} target="_blank" rel="noopener noreferrer" > <span class="sr-only">{link.label}</span> <!-- Icon SVGs here --> <svg class="h-5 w-5" fill="currentColor" viewBox="0 0 24 24"> <path d="M12 0C5.373 0 0 5.373 0 12s5.373 12 12 12 12-5.373 12-12S18.627 0 12 0z" /> </svg> </a> ))} </div> </div> </div>
<div class="mt-8 border-t border-border pt-8 text-center"> <p class="text-xs text-foreground/60"> © {currentYear} Your Name. All rights reserved. </p> </div> </div></footer>Base Layout
Section titled “Base Layout”import BaseLayout from '@/layouts/BaseLayout.astro';import { getCollection } from 'astro:content';
const projects = await getCollection('projects', ({ data }) => !data.draft);***<BaseLayout title="Projects" description="A showcase of my recent work and side projects."> <div class="container py-12"> <h1 class="mb-8 text-4xl font-bold">Projects</h1> <div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> {projects.map((project) => ( <article class="group"> <a href={`/projects/${project.slug}`} class="block"> <h2 class="text-xl font-semibold group-hover:text-primary-600"> {project.data.title} </h2> <p class="mt-2 text-foreground/60">{project.data.description}</p> </a> </article> ))} </div> </div></BaseLayout>Security Headers
Section titled “Security Headers”Cloudflare Pages note: Static deployments of Cloudflare Pages do not read Netlify-style
_headersby default. Configure headers in the Headers Rules UI or inject them via_worker.js. The example below keeps Netlify syntax for portability, but add equivalent rules in Cloudflare’s dashboard if you deploy there.
/* X-Frame-Options: DENY X-Content-Type-Options: nosniff Referrer-Policy: strict-origin-when-cross-origin Permissions-Policy: camera=(), microphone=(), geolocation=() Content-Security-Policy: default-src 'self'; script-src 'self' 'strict-dynamic' https:; style-src 'self' 'nonce-<ASTRO_NONCE>'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://vitals.vercel-insights.com; frame-ancestors 'none';
/ Link: </_astro/inter-latin-400-normal.woff2>; rel=preload; as=font; type=font/woff2; crossorigin=anonymous Link: </_astro/inter-latin-700-normal.woff2>; rel=preload; as=font; type=font/woff2; crossorigin=anonymous// src/components/a11y/SkipLink.astro<a href="#main-content" class="skip-link"> Skip to main content</a>
<style> .skip-link { position: absolute; top: -40px; left: 0; background: var(--color-primary-600); color: white; padding: 8px 16px; text-decoration: none; border-radius: 0 0 4px 0; z-index: 100; transition: top 0.2s; }
.skip-link:focus { top: 0; }</style>404 Page
Section titled “404 Page”import BaseLayout from '@/layouts/BaseLayout.astro';
<BaseLayout title="404 - Page Not Found" description="The page you're looking for doesn't exist." noindex={true}> <div class="container flex min-h-[60vh] flex-col items-center justify-center text-center"> <h1 class="mb-4 text-6xl font-bold">404</h1> <p class="mb-8 text-xl text-foreground/60"> Oops! The page you're looking for doesn't exist. </p> <a href="./" class="rounded-lg bg-primary-600 px-6 py-3 text-white hover:bg-primary-700 focus-visible-ring" > Go Home </a> </div></BaseLayout>Performance Baseline Script
Section titled “Performance Baseline Script”import { exec } from 'child_process';import { writeFileSync } from 'fs';import { promisify } from 'util';
const execAsync = promisify(exec);
async function measureBaseline() { console.log('📊 Measuring performance baseline...');
// Build the site first await execAsync('pnpm run build');
// Start preview server const preview = exec('pnpm run preview');
// Wait for server to start await new Promise(resolve => setTimeout(resolve, 3000));
// Run Lighthouse const { stdout } = await execAsync( 'npx lighthouse http://localhost:4321 --output=json --output-path=./perf-baseline/baseline.json --only-categories=performance,accessibility,best-practices,seo' );
// Parse results const results = JSON.parse(stdout); const scores = { performance: results.categories.performance.score * 100, accessibility: results.categories.accessibility.score * 100, bestPractices: results.categories['best-practices'].score * 100, seo: results.categories.seo.score * 100, };
// Save baseline writeFileSync( './perf-baseline/scores.json', JSON.stringify(scores, null, 2) );
console.log('✅ Baseline saved:', scores);
// Cleanup preview.kill();}
measureBaseline().catch(console.error);