astro-v5-template Svelte Themes

Astro V5 Template

Production-ready Astro 5 starter template with Tailwind CSS v4, Atlas Design System, Svelte 5, and ready-to-use components

Astro v5 Workspace

Production-ready Astro v5 monorepo with pnpm workspaces and shared design system.

✨ Version 1.4.1 - What's New

Interactive Maps & Performance Monitoring

  • πŸ—ΊοΈ Leaflet.js Integration - GDPR-compliant interactive maps
    • Interactive map page with consent banner (/map)
    • Static pre-generated map on contact page (fully GDPR-compliant)
    • Berlin TV Tower location with marker
    • Local hosting (no CDN dependencies)
  • πŸ“Š Web Vitals Monitoring - @casoon/astro-webvitals v0.1.3
    • Core Web Vitals tracking (LCP, FID, CLS, FCP, TTFB, INP)
    • Debug overlay with real-time metrics
    • Accessibility checks (WCAG 2.1)
    • Extended metrics and performance budgets

Technical Improvements

  • πŸ”§ Engine Requirements - Updated to support Node.js >=20.0.0
  • πŸ“ Fixed GitHub Links - Corrected repository links in demo site
  • πŸ“š Enhanced Documentation - Comprehensive README updates

✨ Version 1.4.0 - Previous Release

Blog System Improvements

  • πŸ“ Enhanced BlogCard Component - Improved visual hierarchy and readability
    • Taller teaser images (320px height) for better visual impact
    • Fixed borders on light backgrounds (visible gray borders)
    • Removed non-existent cs-* classes, now using standard Tailwind
    • Better tag styling with proper contrast
    • Gradient text on "Artikel lesen" link with colored icon
  • πŸ“„ Improved Blog Post Layout - Better typography and formatting
    • Sticky header for better navigation
    • Optimized prose classes for article content
    • Fixed code block styling (dark background without white overlay)
    • Image source attribution support
    • Better spacing and hierarchy
  • 🌍 German Localization - All packages now use German locale (de) by default
  • ✍️ Rich Example Content - Comprehensive blog post example with 1,500+ words

Environment & Configuration

  • πŸ”§ Centralized Environment Validation - Type-safe env vars across all packages
  • πŸ“¦ Shared Package Enhancement - Better component reusability
  • 🎨 Consistent Design System - Unified styling across all packages

Packages

packages/
β”œβ”€β”€ shared/  - Shared CSS variables and design system
β”œβ”€β”€ blank/   - Absolute minimum (blank canvas)
β”œβ”€β”€ base/    - Moderate starter with components
└── demo/    - Full-featured demo site

@astro-v5/shared

Shared design system for all packages:

  • CSS variables and design tokens
  • Common animations
  • Utility classes
  • Typography system
  • Dark mode support

@astro-v5/blank πŸ†•

Absolute minimum - Perfect blank canvas:

  • βœ… Single homepage + 404
  • βœ… Basic layout & SEO
  • βœ… Shared design system
  • ❌ No components
  • ❌ No blog
  • ❌ No forms
  • Use when: Starting completely from scratch

@astro-v5/base

Moderate starter with essential features:

  • βœ… All components (Hero, Card, Modal, etc.)
  • βœ… Blog functionality (MDX)
  • βœ… Contact form
  • βœ… Newsletter signup
  • βœ… Example blog post
  • Use when: Need a solid foundation with reusable components

@astro-v5/demo

Full showcase with all features:

  • βœ… 4 example blog posts
  • βœ… All components demonstrated
  • βœ… API examples
  • βœ… Contact forms
  • βœ… Deployed demo site
  • Use when: Exploring features or need complete examples

πŸš€ Quick Start

Prerequisites

# Install Volta (automatic Node.js version management)
curl https://get.volta.sh | bash

# Install pnpm
volta install pnpm

Volta automatically uses Node.js 24.11.0 and pnpm 9.15.4 (defined in package.json and .nvmrc).

Install & Run

# Clone repository
git clone https://github.com/casoon/astro-v5-template.git
cd astro-v5-template

# Install all dependencies
pnpm install

# Run demo site (default)
pnpm dev

# Or run specific package
pnpm dev:blank   # Blank template
pnpm dev:base    # Base template
pnpm dev:demo    # Demo site

πŸ“‹ Workspace Commands

Command Description
pnpm dev Start demo site (default)
pnpm dev:blank Start blank template
pnpm dev:base Start base template
pnpm dev:demo Start demo site
pnpm build Build all packages
pnpm build:blank Build blank only
pnpm build:base Build base only
pnpm build:demo Build demo only
pnpm preview Preview demo build
pnpm preview:blank Preview blank build
pnpm preview:base Preview base build
pnpm preview:demo Preview demo build
pnpm optimize-images Optimize images for all packages
pnpm optimize-images:blank Optimize images for blank only
pnpm optimize-images:base Optimize images for base only
pnpm optimize-images:demo Optimize images for demo only
pnpm clean Clean all packages
pnpm clean:images Remove all optimized images
pnpm format Format all code
pnpm check Run all checks

🎯 Use Cases

Choose Your Starting Point

Decision Tree:

  • πŸ†• Complete blank slate? β†’ Use @astro-v5/blank
  • πŸ—οΈ Need components & blog? β†’ Use @astro-v5/base
  • πŸ“š Want to see examples? β†’ Use @astro-v5/demo

Scenario 1: Start Fresh (Blank Template)

Perfect for custom projects where you want complete control:

# Copy blank package
cp -r packages/blank my-project
cd my-project
pnpm install
pnpm dev

What you get:

  • βœ… Single homepage + 404 page
  • βœ… Basic layout structure
  • βœ… SEO components from shared
  • βœ… Shared design system
  • βœ… Environment configuration
  • ❌ No pre-built components
  • ❌ No blog functionality

Scenario 2: Start with Components (Base Template)

Get essential components and blog functionality out of the box:

# Copy base package
cp -r packages/base my-project
cd my-project
pnpm install
pnpm dev

What you get:

  • βœ… All UI components (Hero, Card, Modal, Forms, etc.)
  • βœ… Blog with MDX support
  • βœ… Contact form
  • βœ… Newsletter signup
  • βœ… Example blog post
  • βœ… Comprehensive component library

Scenario 3: Multiple Sites with Shared Design System

Main use case - multiple landing pages sharing common styles:

# 1. Add new site to workspace
mkdir packages/landing-page

# 2. Copy base template
cp -r packages/base/* packages/landing-page/

# 3. Edit package.json
# Change "name": "@astro-v5/landing-page"
{
  "name": "@astro-v5/landing-page",
  "version": "1.0.0",
  "dependencies": {
    "@astro-v5/shared": "workspace:*"
  }
}
# 4. Install and run
pnpm install
pnpm --filter @astro-v5/landing-page dev

# 5. Add convenience script to root package.json
{
  "scripts": {
    "dev:landing": "pnpm --filter @astro-v5/landing-page dev"
  }
}

Scenario 4: Customize Shared Design System

Edit shared styles once, applies to all packages:

# Edit shared CSS variables
packages/shared/src/styles/variables.css

# Changes automatically apply to:
# - packages/base
# - packages/demo
# - packages/landing-page
# - Any other packages using @astro-v5/shared

πŸ—οΈ Workspace Structure

astro-v5-template/
β”œβ”€β”€ package.json              # Root workspace config
β”œβ”€β”€ pnpm-workspace.yaml       # Workspace definition
β”œβ”€β”€ pnpm-lock.yaml           # Shared lockfile
β”œβ”€β”€ packages/
β”‚   β”œβ”€β”€ shared/              # Shared design system
β”‚   β”‚   β”œβ”€β”€ package.json     # @astro-v5/shared
β”‚   β”‚   β”œβ”€β”€ README.md
β”‚   β”‚   └── src/
β”‚   β”‚       └── styles/
β”‚   β”‚           β”œβ”€β”€ index.css       # Main entry
β”‚   β”‚           β”œβ”€β”€ variables.css   # CSS variables
β”‚   β”‚           β”œβ”€β”€ animations.css  # Animations
β”‚   β”‚           └── utilities.css   # Utility classes
β”‚   β”œβ”€β”€ blank/               # Blank template (minimal)
β”‚   β”‚   β”œβ”€β”€ package.json     # @astro-v5/blank
β”‚   β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ public/
β”‚   β”‚   └── ...
β”‚   β”œβ”€β”€ base/                # Base template (with components)
β”‚   β”‚   β”œβ”€β”€ package.json     # @astro-v5/base
β”‚   β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”œβ”€β”€ public/
β”‚   β”‚   └── ...
β”‚   └── demo/                # Demo site (full showcase)
β”‚       β”œβ”€β”€ package.json     # @astro-v5/demo
β”‚       β”œβ”€β”€ src/
β”‚       β”œβ”€β”€ public/
β”‚       └── ...
└── README.md               # This file

πŸ”„ Workflow Benefits

Shared Design System

  • Update CSS variables in packages/shared β†’ all packages get updates
  • Consistent design tokens across all sites
  • Single source of truth for styles

Independent Deployment

  • Each package can be deployed separately
  • Different content, same tech stack
  • Build and preview independently

Efficient Development

  • Shared node_modules saves disk space
  • Fast installs with pnpm
  • Parallel builds with -r flag
  • Automatic dependency linking with workspace:*

πŸ“ Adding New Sites (Step-by-Step)

Step 1: Create Package Directory

mkdir packages/my-new-site

Step 2: Copy Template

Choose your starting point:

# Option A: Start with blank (minimal)
cp -r packages/blank/* packages/my-new-site/

# Option B: Start with base (includes components & blog)
cp -r packages/base/* packages/my-new-site/

Step 3: Update package.json

Edit packages/my-new-site/package.json:

{
  "name": "@astro-v5/my-new-site",
  "version": "1.0.0",
  "description": "My new site description",
  "dependencies": {
    "@astro-v5/shared": "workspace:*",
    // ... other dependencies are already included
  }
}

Key points:

  • Change name to unique package name with @astro-v5/ prefix
  • Keep "@astro-v5/shared": "workspace:*" to use shared styles
  • Don't remove other dependencies

Step 4: Customize Site Content

# Edit homepage
packages/my-new-site/src/pages/index.astro

# Add blog posts
packages/my-new-site/src/content/blog/

# Customize components
packages/my-new-site/src/components/

Step 5: Install Dependencies

# From root directory
pnpm install

This links the shared package automatically.

Step 6: Run Development Server

pnpm --filter @astro-v5/my-new-site dev

Step 7: Add Convenience Script (Optional)

Edit root package.json to add shortcut:

{
  "scripts": {
    "dev:my-site": "pnpm --filter @astro-v5/my-new-site dev",
    "build:my-site": "pnpm --filter @astro-v5/my-new-site build"
  }
}

Now you can run:

pnpm dev:my-site
pnpm build:my-site

🎨 Using Shared Design System

Import in Layouts

All packages import shared styles via global.css:

/* packages/*/src/styles/global.css */
@import '@astro-v5/shared/styles/index.css';

Available CSS Variables

/* Colors */
var(--color-background)
var(--color-text-primary)
var(--color-accent-primary)
var(--color-border)

/* Spacing */
var(--spacing-xs) through var(--spacing-3xl)

/* Typography */
var(--font-size-base)
var(--font-weight-medium)
var(--line-height-normal)

/* Effects */
var(--shadow-lg)
var(--blur-md)
var(--radius-xl)

See full list in packages/shared/README.md.

Utility Classes

<!-- Navigation -->
<a class="nav-link">Link</a>

<!-- Cards -->
<div class="card-container">Content</div>
<div class="glass-effect">Glass morphism</div>

<!-- Effects -->
<span class="text-gradient">Gradient Text</span>
<div class="hover-lift">Hover effect</div>

<!-- Animations -->
<div class="animate-fade-in-up">Fade in</div>
<div class="animate-float">Float</div>

πŸ—ΊοΈ Sitemap & SEO

Custom Sitemap Implementation

Each package includes a custom sitemap generator (not using @astrojs/sitemap) for full control over SEO parameters.

Location:

  • packages/demo/src/pages/sitemap.xml.ts
  • packages/base/src/pages/sitemap.xml.ts
  • Shared utilities: @shared/utils/sitemap

Advantages:

  • βœ… Full control over priority, changefreq, and lastmod
  • βœ… Automatically scans all .astro pages
  • βœ… Supports blog posts via Content Collections (demo package)
  • βœ… Uses env.PUBLIC_SITE_URL from environment config
  • βœ… Generates sitemap.xml at build time

Example Usage:

// packages/demo/src/pages/sitemap.xml.ts
import { generateSitemapPages, generateSitemapXML } from '@shared/utils/sitemap';
import { getCollection } from 'astro:content';

const pageModules = import.meta.glob('./**/*.astro', { eager: true });
const blogPosts = await getCollection('blog');

const pages = generateSitemapPages({
  siteUrl: env.PUBLIC_SITE_URL,
  pageModules,
  blogPosts, // Optional: only for packages with blog
});

const sitemap = generateSitemapXML(pages, env.PUBLIC_SITE_URL);

Customization:

Edit packages/shared/src/utils/sitemap.ts to adjust:

  • Default priority values
  • Changefreq settings
  • URL filtering logic
  • lastmod date handling

Output: /sitemap.xml (available at https://yourdomain.com/sitemap.xml)

βš™οΈ Configuration & Validation

Environment Variables

Each package uses Zod for type-safe environment configuration:

File: packages/*/src/env.ts

import { z } from 'zod';

const envSchema = z.object({
  PUBLIC_SITE_URL: z.string().url().default('http://localhost:4321'),
  PUBLIC_SITE_NAME: z.string().default('Astro V5 Template'),
});

export const env = envSchema.parse(import.meta.env);

Single Source of Truth:

  • Site URL is defined ONLY in env.ts
  • astro.config.mjs imports from env.ts:
import { env } from './src/env.ts';

export default defineConfig({
  site: env.PUBLIC_SITE_URL, // Import from env
});

This ensures consistency across your configuration.

Build-Time Validation

The template includes automatic configuration validation during builds:

Run manually:

node scripts/validate-config.mjs packages/demo

Automatically runs during:

pnpm build        # Validates all packages
pnpm build:demo   # Validates demo only

What it checks:

  • ❌ ERROR if PUBLIC_SITE_URL uses default/localhost (blocks build)
  • ⚠️ WARNING if PUBLIC_SITE_NAME uses default template name
  • ⚠️ WARNING if package.json still has template metadata
  • ⚠️ WARNING if robots.txt references template URLs
  • ⚠️ WARNING if favicon is missing

Example output:

πŸ” Validating configuration for: demo

βœ“ Checking env.ts configuration...
  ❌ ERROR: PUBLIC_SITE_URL is still using default value
  β†’ Action: Update PUBLIC_SITE_URL in packages/demo/src/env.ts

βœ“ Checking package.json metadata...
  ⚠️  WARNING: repository URL still points to template
  β†’ Action: Update repository URL in packages/demo/package.json

Configuration validation complete!
Found 1 error(s) and 1 warning(s)

Key behavior:

  • Errors block the build (exit code 1)
  • Warnings show but continue (exit code 0)
  • Helps catch template placeholders before deployment

SEO Components

Each package includes comprehensive SEO components from the shared package:

Import from shared:

---
import PageSEO from '@astro-v5/shared/components/seo/PageSEO.astro';
import BlogSEO from '@astro-v5/shared/components/seo/BlogSEO.astro';
---

PageSEO Component:

---
// Only title is required - everything else is optional
import PageSEO from '@astro-v5/shared/components/seo/PageSEO.astro';
---

<head>
  <PageSEO 
    title="About Us"
    description="Learn more about our company"
    keywords={['company', 'about', 'team']}
    image="/responsive/team-photo-800w.webp"
    author="John Doe"
  />
</head>

BlogSEO Component:

---
// Required: title, publishDate, slug
// Optional: Everything else
import BlogSEO from '@astro-v5/shared/components/seo/BlogSEO.astro';
---

<head>
  <BlogSEO
    title={post.data.title}
    publishDate={post.data.publishDate}
    slug={post.slug}
    description={post.data.description}
    author={post.data.author}
    category={post.data.category}
    readingTime={readingTime}
    image={post.data.heroImage}
    keywords={post.data.tags}
  />
</head>

Features:

  • βœ… JSON-LD structured data (WebPage, Article schemas)
  • βœ… Open Graph meta tags
  • βœ… Twitter Card meta tags
  • βœ… Graceful handling of missing values (no errors, no placeholders)
  • βœ… Automatic fallbacks (description defaults to title)
  • βœ… Reading time calculation for blog posts

Graceful Degradation:

  • Missing optional props are simply omitted from output
  • No error messages or placeholder text
  • Only required fields are enforced

See packages/shared/src/components/seo/README.md for full documentation.

πŸ–ΌοΈ Image Optimization

Automatic WebP/AVIF Generation

The template includes a powerful image optimization system that generates optimized images at build-time.

Folder Structure:

packages/your-package/
β”œβ”€β”€ src/
β”‚   └── assets/
β”‚       └── images/          # Source images (JPG, PNG, WebP, SVG)
β”‚           β”œβ”€β”€ hero.jpg
β”‚           β”œβ”€β”€ logo.svg     # SVGs are copied as-is
β”‚           β”œβ”€β”€ photo.webp   # WebP sources also supported
β”‚           └── blog/
β”‚               └── post.jpg
β”‚
└── public/
    └── responsive/          # Generated optimized images
        β”œβ”€β”€ hero-378w.webp
        β”œβ”€β”€ hero-400w.webp
        β”œβ”€β”€ hero-756w.webp
        β”œβ”€β”€ hero-800w.webp
        β”œβ”€β”€ hero-1200w.webp
        β”œβ”€β”€ hero-378w.avif
        β”œβ”€β”€ hero-400w.avif
        β”œβ”€β”€ hero-756w.avif
        β”œβ”€β”€ hero-800w.avif
        β”œβ”€β”€ hero-1200w.avif
        β”œβ”€β”€ hero.jpg         # Original (optimized)
        β”œβ”€β”€ logo.svg         # SVG (copied)
        └── manifest.json

Generate Optimized Images:

# Run when adding NEW images to src/assets/images/
pnpm optimize-images              # All packages
pnpm optimize-images:demo         # Demo package only
pnpm optimize-images:base         # Base package only

Supported Formats:

  • JPG/PNG β†’ Converted to WebP + AVIF (multiple sizes)
  • WebP β†’ Copied + generated in multiple sizes + AVIF variants
  • SVG β†’ Copied as-is (no conversion)

Excluded Files:

  • Favicon files (automatically skipped)
  • Already optimized files (pattern: -\d+w\.(webp|avif|jpg|png)$)

Important: Optimized images should be committed to git (not regenerated at build time). This ensures:

  • Faster CI/CD builds
  • Consistent output across environments
  • Works on Cloudflare Pages and other static hosts

Configuration:

Default settings in scripts/optimize-images.mjs:

{
  inputDir: "src/assets/images",      // Source directory
  outputDir: "public/responsive",      // Output directory
  widths: [378, 400, 756, 800, 1200], // Responsive breakpoints
  formats: ["webp", "avif"],          // Output formats
  quality: {
    webp: 80,
    avif: 75,
    jpeg: 85
  }
}

Workflow:

  1. Add images to src/assets/images/ (JPG, PNG, WebP, or SVG)
  2. Run pnpm optimize-images (or package-specific command)
  3. Generated files appear in public/responsive/
  4. Reference optimized images in your code: /responsive/hero-800w.webp

Output Files:

  • Multiple widths: 378w, 400w, 756w, 800w, 1200w
  • Multiple formats: WebP, AVIF
  • Original format (optimized JPEG or copied WebP)
  • Manifest JSON for programmatic access

Customization: Edit scripts/optimize-images.mjs to change widths, formats, quality settings, or skip patterns.

πŸ—ΊοΈ Interactive Maps

Leaflet.js Integration

The demo package includes both interactive and static map implementations:

Interactive Map (/map):

  • GDPR-compliant with consent banner
  • Leaflet.js hosted locally (no CDN)
  • OpenStreetMap tiles loaded after user consent
  • Berlin TV Tower location with marker
  • Fully responsive design

Static Map (/contact):

  • Pre-generated static map image
  • No external requests (fully GDPR-compliant)
  • Shows Berlin TV Tower location
  • Includes proper OpenStreetMap attribution
  • 800x600px optimized PNG

Usage:

---
// Interactive map with consent
import 'leaflet/dist/leaflet.css';
---
<script>
  import L from 'leaflet';
  // Map loads only after user consent
</script>

Dependencies:

  • leaflet (1.9.4) - For interactive maps
  • @types/leaflet - TypeScript definitions

πŸ“Š Web Vitals Monitoring

@casoon/astro-webvitals

The template includes @casoon/astro-webvitals for performance monitoring:

Features:

  • Core Web Vitals tracking (LCP, FID, CLS, FCP, TTFB, INP)
  • Debug overlay with real-time metrics
  • Accessibility checks (WCAG 2.1)
  • Extended metrics (memory, network)
  • Batch reporting for analytics
  • Performance budget tracking

Current Version: 0.1.3

Usage in Demo:

---
import { WebVitals } from '@casoon/astro-webvitals';
---

<WebVitals 
  debug={import.meta.env.DEV}
  position="bottom-left"
  checkAccessibility={true}
  extendedMetrics={true}
/>

The demo package shows both the local WebVitals component (top-right) and @casoon/astro-webvitals (bottom-left) for comparison.

🎨 Tech Stack

Technology Version Purpose
Astro 5.15.4 Static site generator
Tailwind CSS 4.1.17 Utility-first CSS
Svelte 5.43.5 Reactive components
TypeScript 5.9.3 Type safety
Leaflet 1.9.4 Interactive maps
@casoon/astro-webvitals 0.1.3 Performance monitoring
Sharp 0.34.5 Image optimization
pnpm 9.15.4 Package manager
Volta - Node.js version manager
Biome 2.3.4 Linter & formatter
Zod 4.1.12 Runtime validation
MDX 4.3.10 Markdown with components

🚒 Deployment

Each package can be deployed independently:

Deploy Demo:

# Build demo
pnpm build:demo

# Deploy from packages/demo/dist/

Deploy Base:

# Build base
pnpm build:base

# Deploy from packages/base/dist/

Supported Platforms

  • Vercel
  • Netlify
  • Cloudflare Pages (Node.js version pinned via .nvmrc)
  • GitHub Pages
  • Any static host

Node.js Version Control

The project uses Node.js 24.11.0 (LTS). Version is controlled via:

  • .nvmrc - For nvm, Volta, and Cloudflare Pages
  • .node-version - For asdf and other version managers
  • package.json engines field - For npm/pnpm version checks

Cloudflare Pages will automatically use the version specified in .nvmrc.

πŸ”§ Troubleshooting

"Module not found" errors

# Reinstall workspace
pnpm install

"git can't be found" (Husky warning)

# Husky looks for .git in package root
# This is normal in workspaces, can be ignored
# Or configure husky in root if needed

Different Node.js versions

# Volta manages versions automatically
# Just ensure Volta is installed
volta install [email protected]
volta install pnpm

# Or use nvm
nvm use

# Or use asdf
asdf install

Shared styles not updating

# Clear cache and rebuild
pnpm clean
pnpm install
pnpm build

πŸ“š Learn More

License

MIT Β© casoon

Top categories

Loading Svelte Themes