Production-ready Astro v5 monorepo with pnpm workspaces and shared design system.
/map)cs-* classes, now using standard Tailwindde) by defaultpackages/
βββ shared/ - Shared CSS variables and design system
βββ blank/ - Absolute minimum (blank canvas)
βββ base/ - Moderate starter with components
βββ demo/ - Full-featured demo site
Shared design system for all packages:
Absolute minimum - Perfect blank canvas:
Moderate starter with essential features:
Full showcase with all features:
# 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).
# 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
| 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 |
Decision Tree:
@astro-v5/blank@astro-v5/base@astro-v5/demoPerfect 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:
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:
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"
}
}
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
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
packages/shared β all packages get updatesnode_modules saves disk space-r flagworkspace:*mkdir packages/my-new-site
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/
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:
name to unique package name with @astro-v5/ prefix"@astro-v5/shared": "workspace:*" to use shared styles# 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/
# From root directory
pnpm install
This links the shared package automatically.
pnpm --filter @astro-v5/my-new-site dev
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
All packages import shared styles via global.css:
/* packages/*/src/styles/global.css */
@import '@astro-v5/shared/styles/index.css';
/* 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.
<!-- 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>
Each package includes a custom sitemap generator (not using @astrojs/sitemap) for full control over SEO parameters.
Location:
packages/demo/src/pages/sitemap.xml.tspackages/base/src/pages/sitemap.xml.ts@shared/utils/sitemapAdvantages:
priority, changefreq, and lastmod.astro pagesenv.PUBLIC_SITE_URL from environment configsitemap.xml at build timeExample 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:
Output: /sitemap.xml (available at https://yourdomain.com/sitemap.xml)
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:
env.tsastro.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.
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:
PUBLIC_SITE_URL uses default/localhost (blocks build)PUBLIC_SITE_NAME uses default template nameExample 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:
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:
Graceful Degradation:
See packages/shared/src/components/seo/README.md for full documentation.
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:
Excluded Files:
-\d+w\.(webp|avif|jpg|png)$)Important: Optimized images should be committed to git (not regenerated at build time). This ensures:
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:
src/assets/images/ (JPG, PNG, WebP, or SVG)pnpm optimize-images (or package-specific command)public/responsive//responsive/hero-800w.webpOutput Files:
Customization: Edit scripts/optimize-images.mjs to change widths, formats, quality settings, or skip patterns.
The demo package includes both interactive and static map implementations:
Interactive Map (/map):
Static Map (/contact):
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 definitionsThe template includes @casoon/astro-webvitals for performance monitoring:
Features:
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.
| 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 |
Each package can be deployed independently:
# Build demo
pnpm build:demo
# Deploy from packages/demo/dist/
# Build base
pnpm build:base
# Deploy from packages/base/dist/
.nvmrc)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 managerspackage.json engines field - For npm/pnpm version checksCloudflare Pages will automatically use the version specified in .nvmrc.
# Reinstall workspace
pnpm install
# Husky looks for .git in package root
# This is normal in workspaces, can be ignored
# Or configure husky in root if needed
# 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
# Clear cache and rebuild
pnpm clean
pnpm install
pnpm build
MIT Β© casoon