A modern marketing website built with Astro for blazing-fast static site generation and deployed to Cloudflare Workers edge locations worldwide.
We're running Astro 5 with React 19 islands for interactive components, styled with Tailwind CSS v4, and deployed as a static site to Cloudflare's global edge network. Because nothing says "fast" like serving HTML from 300+ locations worldwide.
- Astro 5: Static site generation with islands architecture for optimal performance
- React 19: For interactive components where you actually need JavaScript (sparingly)
- Tailwind CSS v4: Lightning-fast utility-first CSS with the new CSS-based config
- Cloudflare Workers: Deploy once, run everywhere at the edge
- Bun: Because npm is so last year (and it's genuinely faster)
- TypeScript: Catching bugs at compile time since 2012
web/
├── layouts/ # Astro layout components
│ └── BaseLayout.astro # Main layout with header/footer
├── lib/ # Utilities and helpers
│ └── utils.ts # Shared utility functions
├── pages/ # Astro pages (file-based routing)
│ ├── index.astro # Landing page
│ ├── features.astro # Features showcase
│ ├── pricing.astro # Pricing tiers
│ └── about.astro # About page
├── public/ # Static assets (favicon, images, etc.)
├── styles/ # Global styles
│ └── globals.css # CSS variables and base styles
├── astro.config.mjs # Astro configuration
├── tailwind.config.css # Tailwind CSS v4 config
├── postcss.config.js # PostCSS plugins
└── wrangler.jsonc # Cloudflare Workers deployment config
# Install dependencies (from monorepo root)
bun install
# Start development server
bun web:dev
# or
bun --filter @repo/web dev
# Build for production
bun web:build
# or
bun --filter @repo/web buildThe site will be available at http://localhost:4321 (Astro's default port, because 3000 was too mainstream).
# Development
bun dev # Start dev server with hot reload
bun build # Build static site to dist/
bun preview # Preview production build locally
bun check # Type-check .astro files
# Deployment
bun deploy # Deploy to Cloudflare Workers (production)
bun deploy:preview # Deploy to preview environmentWe're using the latest Tailwind CSS v4 with its new CSS-based configuration. No more JavaScript config files!
The tailwind.config.css file uses CSS directives:
@import "tailwindcss";
/* Tell Tailwind where to look for classes */
@source "./pages/**/*.{astro,js,ts,jsx,tsx}";
@source "./layouts/**/*.{astro,js,ts,jsx,tsx}";
@source "../../packages/ui/components/**/*.{ts,tsx}";
/* Custom dark mode variant */
@custom-variant dark (&:is(.dark *));
/* Theme configuration */
@theme inline {
--color-primary: var(--primary);
--color-background: var(--background);
/* ... more theme tokens */
}Our design system uses CSS custom properties for theming:
:root {
--background: oklch(1 0 0);
--foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0);
/* Light mode colors */
}
.dark {
--background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0);
/* Dark mode colors */
}We use OKLCH color space because RGB is for screens from the 90s.
Pure Astro components for maximum performance:
---
// BaseLayout.astro
export interface Props {
title?: string;
description?: string;
}
const { title = 'Your Site Title', description } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<title>{title}</title>
<meta name="description" content={description} />
</head>
<body>
<slot />
</body>
</html>Interactive components using React (only where needed):
---
import { Button } from '@repo/ui';
---
<!-- Only hydrate on visibility for performance -->
<Button client:visible>
Interactive Button
</Button>
<!-- Or load immediately if critical -->
<Button client:load>
Critical Button
</Button>We import components from @repo/ui - our shared component library powered by shadcn/ui:
import { Button, Card, Badge } from "@repo/ui";Astro uses file-based routing. Each .astro file in pages/ becomes a route:
pages/index.astro→/pages/about.astro→/aboutpages/features.astro→/featurespages/pricing.astro→/pricing
For dynamic content (if needed):
---
// pages/blog/[slug].astro
export async function getStaticPaths() {
return [
{ params: { slug: 'hello-world' } },
{ params: { slug: 'astro-is-awesome' } },
];
}
const { slug } = Astro.params;
---
<h1>Blog post: {slug}</h1>- Static Generation: Pre-rendered HTML for instant loading
- Zero JavaScript by Default: JS only loads for interactive islands
- Automatic Image Optimization: Via Astro's Image component
- CSS Extraction: All styles bundled and minified
- Asset Hashing: Optimal browser caching
We aim for (and usually hit) perfect scores:
- Performance: 100
- Accessibility: 100
- Best Practices: 100
- SEO: 100
Because if you're not getting 100s, are you even trying? 😎
The site deploys to Cloudflare Workers Sites for edge delivery:
# Deploy to production
bun deploy
# Deploy to preview environment
bun deploy:previewThe wrangler.jsonc handles deployment:
# .env.local (for local development)
PUBLIC_SITE_URL=http://localhost:4321
PUBLIC_API_URL=http://localhost:3000Note: Astro requires PUBLIC_ prefix for client-side variables.
The BaseLayout.astro component handles all SEO meta tags:
- Open Graph tags for social sharing
- Twitter Card meta tags
- Structured data (JSON-LD)
- Canonical URLs
- Sitemap generation
- Use Astro components for static content
- Add React islands sparingly - only for interactivity
- Lazy load images below the fold
- Minimize client-side JavaScript - let Astro do the heavy lifting
- Semantic HTML everywhere
- ARIA labels where needed
- Keyboard navigation for all interactive elements
- Color contrast meeting Web Content Accessibility Guidelines AA standards
- Type everything - TypeScript isn't optional
- Component composition over complex components
- Mobile-first responsive design
- Test on slow connections - not everyone has gigabit
Port already in use: Another process is using port 4321
# Find and kill the process
lsof -i :4321
kill -9 <PID>Tailwind classes not working: Make sure the path is included in @source directive
Build fails on Cloudflare: Check Node.js version compatibility
React components not interactive: Forgot to add client:* directive
# Verbose logging
DEBUG=* bun dev
# Astro debug info
bun astro infoWhen adding new pages or features:
- Keep it static unless interactivity is essential
- Optimize images - use WebP/AVIF formats
- Test performance with Lighthouse
- Check accessibility with axe DevTools
- Preview on mobile - most users aren't on desktops
- Write semantic HTML - divs are not the only element
Remember: The fastest JavaScript is no JavaScript. 🚀
- Astro Documentation
- Tailwind CSS v4 Beta
- Cloudflare Workers Sites
- shadcn/ui Components
- OKLCH Color Space
"Why did the developer use Astro? Because they wanted their site to be out of this world!" 🌟
— Dad Joke Department
{ "name": "your-website-name", "compatibility_date": "2025-08-15", "assets": { "directory": "./dist", }, "build": { "command": "bun run build", }, }