Building This Portfolio with Astro v6
Why Astro?
For a portfolio site with no real-time data requirements, Next.js is overkill. Server components, app router, edge functions — none of that matters when you’re shipping static HTML to Cloudflare Pages.
Astro v6 gives you:
- Zero JS by default — every component is server-rendered HTML unless you explicitly opt into interactivity
- Content Collections — typed, schema-validated Markdown files with a clean API
- Islands Architecture — hydrate only the components that need it
- Tailwind v4 via Vite plugin — no JS config file, pure CSS, on-demand utilities
The result is a site that’s fast by default, not by optimization.
Content Collections Architecture
Every section on this page lives in src/content/*.md. The schema is defined once in src/content.config.ts using a passthrough Zod schema — each markdown file can define whatever frontmatter shape its component needs.
const sections = defineCollection({
loader: glob({ pattern: '*.md', base: './src/content' }),
schema: z.object({ /* common fields */ }).passthrough(),
});
The page loads all sections at build time with getCollection('sections'), builds a keyed map by ID, and passes each section’s data as props to its component.
Benefits:
- Content is the source of truth — no hardcoded strings in components
- Components are pure — they only receive props, never read the filesystem
- Easy to update — change copy without touching component code
Tailwind CSS v4
Tailwind v4 drops the JavaScript config file entirely. Configuration lives in CSS:
@import 'tailwindcss';
@theme {
--font-sans: 'Inter', ui-sans-serif, system-ui;
--animate-orb-drift: orb-drift 14s ease-in-out infinite;
}
The Vite plugin picks this up and generates utilities on demand. No tailwind.config.mjs, no content glob, no purging.
The Theme System
The light/dark theme is pure CSS custom properties. Every component uses var(--bg-card), var(--text-primary), etc. — never hardcoded Tailwind color classes.
:root {
--bg-card: rgba(255, 255, 255, 0.8);
--text-primary: #0f172a;
}
[data-theme="dark"] {
--bg-card: rgba(255, 255, 255, 0.05);
--text-primary: #ffffff;
}
The toggle writes to localStorage and updates data-theme on <html>. An inline script in <head> reads the saved preference before first paint, eliminating flash of unstyled content.
Result
Static HTML. No client-side JS except the theme toggle and scroll animations. Sub-second LCP. Deployed to Cloudflare’s global edge network in seconds.
Astro was the right call.