Skip to content

Quick Deploy

Ready to make the template your own and deploy to production? This guide walks you through personalization, deployment, and verification in under an hour.

Prerequisites: You should have completed the Launch Demo and have the site running locally.

Before starting this guide, ensure you have:

  • Completed Launch Demo successfully
  • Site runs locally without errors (pnpm run dev)
  • No console errors in browser (F12 → Console)
  • pnpm run build succeeds locally (requires SITE_URL — see Step 1.1)
  • Git basics understood (commit, push, remote)
  • Hosting platform account created (or will create during guide)

Why this matters: Deploying a broken local site wastes time. Fix issues locally first.

Test your local build:

Terminal window
pnpm run preview:build # Builds and serves the production build locally

Open http://localhost:4321 - if this works, you’re ready to deploy.

Time Required: 45-75 minutes (first-time users)

Section titled “Time Required: 45-75 minutes (first-time users)”
  • Site personalized with your branding
  • Deployed to production
  • Live at your custom URL
  • Verified and tested
  • Completed Launch Demo - Local site running successfully
  • GitHub account (sign up)
  • Hosting platform account (see Platform Selection below)
  • Basic Git knowledge (commit, push)

Choose your hosting platform before starting:

PlatformBest ForFree TierSetup TimeAuto HTTPSRecommendation
Cloudflare PagesPerformance, global CDNUnlimited sites10-15 min✅ InstantBest for most
VercelNext.js ecosystem, teams100GB/month10-15 min✅ InstantGreat for existing Vercel users
NetlifyForms, split testing, CMS100GB/month10-15 min✅ InstantGood for marketing sites

When you run pnpm run build, here’s what happens:

  1. Environment Validation (env:validate)

    • Checks that SITE_URL (or PUBLIC_SITE_URL) is set to a real URL
    • Fails the build if the variable is missing or still a placeholder (example.com, your-domain, localhost)
  2. Token Compilation (tokens:build)

    • Reads tokens/base.json and tokens/semantic.json
    • Generates CSS custom properties
    • Outputs to tokens/dist/tokens.css and tokens/dist/tailwind-tokens.json (git-ignored, regenerated on every build)
  3. Astro Build (astro build)

    • Compiles .astro components to HTML
    • Bundles JavaScript (only interactive islands)
    • Optimizes CSS (removes unused styles)
    • Processes images (AVIF + WebP)
    • Generates sitemap
  4. Output (dist/)

    • Static HTML files
    • Optimized assets (CSS, JS, images)
    • Public files (favicon, fonts, etc.)

The dist/ folder is what gets deployed - not your source code.

Copy this into your notes and check off as you go:

- [ ] Prerequisites verified
- [ ] Platform account created
- [ ] Repository created on GitHub
- [ ] Site configuration updated
- [ ] Metadata personalized
- [ ] Favicons replaced
- [ ] Design tokens customized (optional)
- [ ] Changes committed to Git
- [ ] Pushed to GitHub
- [ ] Connected to hosting platform
- [ ] Build successful
- [ ] Live site verified
- [ ] Mobile tested

🎨 Step 1: Personalize Your Site (20-30 min)

Section titled “🎨 Step 1: Personalize Your Site (20-30 min)”

File: .env (project root — copy from .env.example)
Why: astro.config.mjs derives site from the SITE_URL (or PUBLIC_SITE_URL) environment variable, which drives SEO, sitemaps, and asset linking. There is no hardcoded URL to edit — and the build fails at the env:validate step if the variable is unset or still a placeholder (example.com, your-domain, localhost).

Terminal window
# .env (copied from .env.example — dev-only default)
PUBLIC_SITE_URL=http://localhost:4321
.env
SITE_URL=https://your-actual-domain.com
# OR use your Cloudflare Pages URL initially:
# SITE_URL=https://my-project.pages.dev
  1. Copy .env.example to .env if you haven’t already (cp .env.example .env)
  2. Set SITE_URL to your domain or your expected *.pages.dev URL
  3. Save the file — astro.config.mjs reads the variable at build time; don’t edit the config itself
  4. In Step 2.2 you’ll add the same SITE_URL as an environment variable on your hosting platform (.env is git-ignored, so the platform can’t see it)

File: src/config.ts
Why: Controls the site title, description, author, and links used in meta tags and social sharing
Lines: ~10-40 (siteMetadata and siteLinks)

Open src/config.ts and update these values (the layout’s <Head /> component reads them, so you don’t need to edit src/layouts/BaseLayout.astro directly):

  • siteMetadata.title'Your Site Name' (appended to page titles as Page Title | Your Site Name, and used for Open Graph/Twitter tags)
  • siteMetadata.description → fallback description for pages that don’t provide their own
  • siteMetadata.author → your name (used in meta tags and structured data)
  • siteLinks.github → your repository URL (used in header, footer, and CTA links)
Show full diff with before/after
src/config.ts
export const siteMetadata = {
title: "Astro Performance Starter",
title: "Your Site Name",
description: "A production-ready Astro starter focused on performance, accessibility, and DX.",
description: "Your site description for SEO",
author: "Your Name",
author: "Jane Developer",
} as const;
export const siteLinks = {
github: "https://github.com/clownware/astro-performance-starter",
github: "https://github.com/YOUR_USERNAME/YOUR_REPO",
// ...
} as const;

Files: public/favicon.svg (and variants)
Why: Your site icon in browser tabs and bookmarks

Specifications:

  • SVG: Recommended, scalable, supports dark mode
  • PNG: Fallback, 32×32px minimum, 512×512px ideal
  • ICO: Legacy support, 16×16 and 32×32 sizes
  1. Create your favicon (use Favicon.io or design tool)
  2. Replace public/favicon.svg with your SVG
  3. (Optional) Add public/favicon.ico for legacy browsers
  4. (Optional) Add public/favicon-32x32.png and public/favicon-16x16.png

Verify:

Terminal window
# Check files exist
ls -la public/favicon*
# Should show:
# favicon.svg (required)
# favicon.ico (optional)
# favicon-32x32.png (optional)

Files:

  • tokens/semantic.json - Brand colors
  • src/components/structural/Header.astro - Logo text

Quick Color Change:

Brand colors live in the role-based semantic.primary and semantic.secondary scales (ADR-047). Each step (50-950) aliases a color ramp defined in tokens/base.json — by default color.violet for primary and color.rose for secondary:

// tokens/semantic.json (excerpt — the real file has the full 50-950 scale)
{
"semantic": {
"primary": {
"500": { "value": "{color.violet.500}" }
},
"secondary": {
"500": { "value": "{color.rose.500}" }
}
}
}

To change brand colors, either repoint the primary/secondary steps at a different base ramp (e.g. {color.amber.500}), or edit the ramp’s HSL channel values in tokens/base.json (e.g. "500": { "value": "256 86% 63%" }).

Rebuild tokens:

Terminal window
pnpm run tokens:build # Compile tokens only
# Or rebuild everything:
pnpm run build

Update the logo:

Replace public/logo.svg with your own logo file. The header (src/components/structural/Header.astro) renders it as an image — update its alt text to your brand name while you’re there.

Before deploying, verify everything looks correct:

Terminal window
# Restart dev server
pnpm run dev
# Open http://localhost:4321

Verification checklist:

  • New title appears in browser tab
  • New favicon displays
  • Updated metadata (view page source: Ctrl/Cmd+U)
  • Logo text updated (if changed)
  • Colors updated (if changed)
  • No console errors (F12 → Console)

🚀 Step 2: Deploy to Production (15-20 min)

Section titled “🚀 Step 2: Deploy to Production (15-20 min)”

Check your Git status first:

Terminal window
# Check if Git is already initialized
git status
# If you see "not a git repository", initialize it:
git init
Terminal window
git add .
git commit -m "feat: personalize site configuration and branding"
git push
Terminal window
# Stage all changes
git add .
# Commit with message
git commit -m "feat: personalize site configuration and branding"

Create GitHub repository:

  1. Go to github.com/new
  2. Name your repository (e.g., my-astro-site)
  3. Choose Public or Private
  4. Do NOT initialize with README (you already have files)
  5. Click Create repository

Connect and push:

Terminal window
# Add remote (replace with your GitHub username and repo name)
git remote add origin https://github.com/YOUR_USERNAME/YOUR_REPO.git
# Rename branch to master (the template's default branch, if needed)
git branch -M master
# Push to GitHub
git push -u origin master
  1. Go to dash.cloudflare.com/sign-up
  2. Enter email and create password
  3. Verify email address
  1. In Cloudflare dashboard, navigate to Workers & Pages
  2. Click Create Application
  3. Select Pages tab
  4. Click Connect to Git
  5. Choose GitHub and authorize Cloudflare
  6. Select your repository from the list
Project name: your-project-name
Production branch: master
Build command: pnpm run build
Build output directory: dist
Root directory: (leave empty)
Environment variables: SITE_URL=https://your-project-name.pages.dev (REQUIRED)
  1. Click Save and Deploy
  2. Wait 2-5 minutes for build to complete
  3. Your site will be live at https://your-project-name.pages.dev

Your site is now live, but it may be using the guessed URL from Step 1.1. Let’s fix that:

  1. Copy your actual URL from Cloudflare (e.g., https://my-project-abc.pages.dev)

  2. Update the platform variable: in your Pages project, go to SettingsEnvironment Variables and set SITE_URL to the actual URL

  3. Update your local .env to match, so local production builds agree with the platform

  4. Trigger a rebuild — changing an environment variable doesn’t redeploy by itself: use DeploymentsRetry deployment, or push any commit

  5. Verify the new build succeeded in Cloudflare dashboard

When and How to Use Environment Variables

When you need environment variables:

  • API keys for third-party services
  • Analytics tracking IDs
  • CMS endpoints
  • Feature flags

Type-Safe Environment Variables with astro:env:

The template already configures astro:env (the top-level env option in astro.config.mjs, see ADR-050) with a schema for its PUBLIC_* variables. Note that SITE_URL, PUBLIC_SITE_URL, and DEPLOY_TARGET are intentionally not in that schema — they’re read at config-load time (before astro:env exists) and validated by the env:validate prebuild script instead. Don’t add them to the schema or re-derive site yourself.

1. Add new variables to the existing schema in astro.config.mjs:

// astro.config.mjs — extend the existing env.schema block
env: {
schema: {
// ...existing PUBLIC_* fields...
API_KEY: envField.string({
context: 'server',
access: 'secret',
}),
},
},

2. Add to your hosting platform:

Cloudflare Pages:

  • Go to SettingsEnvironment Variables
  • Add SITE_URL, API_KEY, etc.
  • Separate variables for Production and Preview environments

Vercel:

  • Go to SettingsEnvironment Variables
  • Add variables with environment selection (Production, Preview, Development)

Netlify:

  • Go to Site settingsEnvironment variables
  • Add variables (applies to all deploys by default)

3. Use in your code:

---
import { API_KEY } from 'astro:env/server';
const response = await fetch('https://api.example.com/endpoint', {
headers: { Authorization: `Bearer ${API_KEY}` },
});
---

Security best practices:

  • Never commit .env files to Git (already in .gitignore)
  • Use access: 'secret' for sensitive data (API keys, tokens)
  • Use access: 'public' for non-sensitive config (site URL, feature flags)

See Vercel Deployment Guide for detailed instructions.

Quick steps:

  1. Go to vercel.com/new
  2. Import your GitHub repository
  3. Build command: pnpm run build
  4. Output directory: dist
  5. Deploy

See Netlify Deployment Guide for detailed instructions.

Quick steps:

  1. Go to app.netlify.com/start
  2. Connect to GitHub
  3. Build command: pnpm run build
  4. Publish directory: dist
  5. Deploy
More Platform Options

Best for: Full-stack apps, databases, background workers

  1. Go to render.com
  2. Click NewStatic Site
  3. Connect GitHub repository
  4. Build command: pnpm run build
  5. Publish directory: dist
  6. Deploy

Pros: Free SSL, global CDN, preview environments

Best for: Open source projects, documentation sites

The template already ships this workflow.github/workflows/deploy.yml builds with the GitHub Pages environment baked in, so there’s nothing to create:

# .github/workflows/deploy.yml (already in the template — build step excerpt)
- name: Build site
run: pnpm run build
env:
DEPLOY_TARGET: gh-pages
SITE_URL: https://${{ github.repository_owner }}.github.io
  1. Enable GitHub Pages:

    • Repo → Settings → Pages
    • Source: GitHub Actions
  2. Push to master — the workflow builds and deploys automatically.

Note: With DEPLOY_TARGET=gh-pages, astro.config.mjs derives the base path (/repo-name) automatically from the name field in package.json — do not hand-add a base to the config. If you rename the repo, update package.json’s name to match. For a custom domain, change SITE_URL in the workflow to your domain and drop DEPLOY_TARGET so the site serves from the root path.

  1. In your Pages project, go to Custom domains
  2. Click Set up a custom domain
  3. Enter your domain (e.g., yourdomain.com)
  4. Follow DNS configuration instructions:
    • Add CNAME record pointing to your-project.pages.dev
    • Or use Cloudflare nameservers (recommended)
  5. Wait for DNS propagation (5-30 minutes)

Update the SITE_URL variable:

  1. In your Pages project, go to SettingsEnvironment Variables and set SITE_URL to https://yourdomain.com
  2. Update your local .env to match
  3. Trigger a rebuild (DeploymentsRetry deployment, or push any commit)

2.5 CI/CD Integration (Optional, Advanced)

Section titled “2.5 CI/CD Integration (Optional, Advanced)”
Custom Deployment Pipelines with GitHub Actions

Why use GitHub Actions instead of native Git integration?

Most platforms (Cloudflare Pages, Vercel, Netlify) automatically deploy when you push to Git. GitHub Actions gives you more control for advanced use cases.

Use cases for GitHub Actions:

  • Run tests before deployment - Prevent broken builds from going live
  • Custom build steps - Complex preprocessing, code generation
  • Multi-environment deploys - Staging, preview, production from one workflow
  • Service integrations - Slack notifications, database migrations, cache invalidation
  • Monorepo deployments - Deploy multiple projects from one repo

If you just need “push to deploy,” stick with native Git integration. GitHub Actions adds complexity.

Setup:

  1. Create workflow file .github/workflows/deploy.yml:
name: Deploy to Cloudflare Pages
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
deployments: write
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 10
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '24'
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build site
run: pnpm run build
- name: Deploy to Cloudflare Pages
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist --project-name=your-project-name
  1. Add secrets to GitHub:

    • Go to your repo → SettingsSecrets and variablesActions
    • Add CLOUDFLARE_API_TOKEN (get from Cloudflare dashboard)
    • Add CLOUDFLARE_ACCOUNT_ID (found in Cloudflare URL)
  2. Get Cloudflare credentials:

    • API Token: Cloudflare dashboard → My Profile → API Tokens → Create Token
    • Use “Edit Cloudflare Workers” template
    • Account ID: In Cloudflare Pages project URL

Vercel automatically deploys on push without additional configuration. Just connect your repo.

Netlify also auto-deploys on push. Configure in netlify.toml:

[build]
command = "pnpm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "24"
PNPM_VERSION = "10"

✓ Step 3: Verify Your Deployment (5-10 min)

Section titled “✓ Step 3: Verify Your Deployment (5-10 min)”

Visit your live site and verify:

  • Site loads without errors
  • Correct title in browser tab
  • Correct favicon displays
  • Homepage displays correctly
  • Showcase page works (/showcase/)
  • Dark mode toggle works
  • Mobile responsive (test on phone or DevTools)
  • No console errors (F12 → Console)
  • No 404 errors for assets

Run a Lighthouse audit to verify performance:

  1. Open your live site
  2. Press F12 → Lighthouse tab
  3. Click Analyze page load
  4. Check scores
  1. Go to pagespeed.web.dev
  2. Enter your live URL
  3. Click Analyze

WCAG AA Compliance Check:

  1. WAVE Browser Extension

  2. Axe DevTools

    • Install: Chrome Web Store
    • Open DevTools (F12) → Axe DevTools tab
    • Click Scan ALL of my page
    • Fix any critical or serious issues
  3. Lighthouse Accessibility Audit

    • Already included in Lighthouse (see Option 1)
    • Target: 100/100 score

Common accessibility checks:

  • All images have alt text
  • Heading hierarchy is logical (h1 → h2 → h3)
  • Color contrast meets WCAG AA (4.5:1 for text)
  • Keyboard navigation works (Tab, Enter, Escape)
  • Focus indicators are visible
  • Form inputs have labels
  • ARIA attributes used correctly

These scores assume:

  • No additional content yet
  • No third-party scripts (analytics, ads)
  • No custom fonts beyond system fonts
  • No large images
MetricTargetWhy This Score
Performance95+Astro’s static output is blazing fast
Accessibility100Semantic HTML + ARIA best practices
Best Practices100HTTPS, modern standards, no deprecated APIs
SEO100Meta tags, sitemap, semantic structure

As you add content, scores will change. This is normal. Focus on:

  • Image optimization (use Astro’s <Image /> component)
  • Lazy loading for non-critical content
  • Keeping third-party scripts minimal

Common score impacts:

  • Performance drops to 85-90 - After adding 10+ images, custom fonts, or analytics
  • Accessibility drops to 95-98 - Missing alt text on new images, contrast issues in custom designs
  • Best Practices drops to 95 - Third-party scripts (Google Analytics, ad networks)
  • SEO stays 100 - Unless you forget meta descriptions or have broken internal links

Goal: Keep Performance > 85, everything else > 95 as you add content.

Quick Wins:

  1. Chrome DevTools Device Mode (Ctrl/Cmd+Shift+M)

    • Test iPhone 14 Pro, Pixel 7, iPad Pro
    • Toggle device toolbar, rotate orientation
    • Good enough for most cases
  2. Free Real Device Testing

    • LambdaTest - 100 minutes/month free
    • BrowserStack - Free trial, then $29-99/month
    • Use if you need cross-browser testing on real devices
  3. Your Own Devices (Recommended)

    • iOS Safari (iPhone/iPad)
    • Android Chrome
    • Desktop browsers (Chrome, Firefox, Safari)
    • Most reliable for catching real issues

Test Checklist:

  • Navigation works on mobile
  • Dark mode toggle accessible
  • Text readable without zooming
  • No horizontal scroll
  • Touch targets ≥ 44×44px
  • Forms usable on mobile (if applicable)
Staying Updated with Astro Releases

Current version: Astro v7.2.2 (see the starter’s package.json for the exact version)

Monitoring updates:

Astro releases new versions regularly. Stay informed to benefit from improvements and avoid breaking changes.

Update strategy:

  1. Follow release channels:

  2. Before upgrading:

    • Review Astro upgrade guide
    • Check for breaking changes in the changelog
    • Test in a separate branch first
  3. Safe upgrade process:

    • Create a new branch: git checkout -b upgrade-astro
    • Update dependencies: pnpm update astro
    • Run build: pnpm run build
    • Test locally: pnpm run preview
    • Fix any breaking changes
    • Merge when stable

Staying updated:

Terminal window
# Check for updates
pnpm outdated
# Update Astro (minor versions)
pnpm update astro
# Update all dependencies
pnpm update
# Major version upgrade (test first!)
pnpm add astro@latest

Cause: Platform doesn’t have pnpm installed

Fix: Update build command to install pnpm first:

Terminal window
npm install -g pnpm && pnpm run build

Or use npm instead:

Terminal window
npm install && npm run build

Cause: Missing token files in repository

Fix:

  1. Verify tokens/base.json and tokens/semantic.json exist locally
  2. Ensure they’re committed to Git:
Terminal window
git add tokens/
git commit -m "fix: add token files"
git push

Cause: Path aliases not resolved

Fix: Ensure tsconfig.json has proper paths configuration (should be default in template). If missing, add:

{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["src/*"],
"@components/*": ["src/components/*"],
"@layouts/*": ["src/layouts/*"],
"@utils/*": ["src/utils/*"]
}
}
}

Fix: Verify the SITE_URL environment variable on your platform matches the deployment URL exactly (including https://)

Terminal window
# ✅ Correct
SITE_URL=https://my-project.pages.dev
# ❌ Incorrect
SITE_URL=my-project.pages.dev # Missing https://
SITE_URL=http://my-project.pages.dev # Wrong protocol

Fix: Ensure platform build settings use dist as output directory

Fix: If deploying to subdirectory, add base to config:

astro.config.mjs
export default defineConfig({
site: 'https://yourdomain.com',
base: '/subdirectory', // Only if deploying to subdirectory
});

Fix: Use Astro’s image component and relative imports:

---
// ✅ Correct
import { Image } from 'astro:assets';
import myImage from '@/assets/image.jpg';
---
<Image src={myImage} alt="Description" />
<!-- ❌ Incorrect -->
<img src="/src/assets/image.jpg" alt="Description" />

Fix: The build script automatically compiles tokens. If styles are still broken:

Terminal window
# Test locally first
pnpm run build
pnpm run preview

If it works locally but not on the platform, check:

  • Build logs for token compilation step
  • Ensure tokens/ directory is committed to Git
  • Verify platform is using pnpm run build (not a custom command)

Cause: Font files or Fonts API config out of sync

Section titled “Cause: Font files or Fonts API config out of sync”

Fix: The template loads fonts through Astro’s Fonts API — the .woff2 files are vendored in src/assets/fonts/ and registered in astro.config.mjs via fontProviders.local(). Verify:

Terminal window
ls src/assets/fonts/*.woff2
  • Font files exist in src/assets/fonts/ and are committed to Git
  • The fonts entries in astro.config.mjs point at the correct file paths
  • No manual <link rel="preload"> tags are needed — the Fonts API generates preload tags automatically
  1. Check build logs on your platform dashboard for detailed error messages
  2. Review deployment guide: Phase 10: Deployment
  3. Common issues: FAQ
  4. Search existing issues: GitHub Issues
  5. Ask for help: GitHub Discussions

Now that you’re deployed, choose your path based on your goals:

Best for: Blogs, portfolios, marketing sites

  1. Creating Your First Page
  2. Content Collections Guide
  3. Add blog posts or projects
  4. Customize page layouts

Time: 1-2 hours to first content page

Best for: Unique branding, custom themes

  1. Design Tokens Guide
  2. Customize colors, typography, spacing
  3. Create custom components
  4. Build design system

Time: 2-4 hours for basic customization

Best for: Complex sites, interactive elements

  1. Component Patterns
  2. Phase 5: Components
  3. Add interactive islands
  4. Implement advanced features

Time: 4-8 hours for advanced features

Best for: Production-ready, feature-complete sites

  1. MVP Track Guide (2-3 weeks)
  2. Showcase Track Guide (4-6 weeks)
  3. Complete all 12 phases
  4. Production optimization

Time: 2-6 weeks depending on track

Actual times for this guide:

PhaseTime
Setup & Personalization20-30 min
Git & Repository Setup5-10 min
Platform Deployment10-20 min
Verification & Testing5-10 min
Custom Domain (optional)+15 min
Total45-75 min

Your site is now live! You’ve successfully:

  • Personalized your branding
  • Deployed to production
  • Verified performance
  • Tested across devices

We’d love to see what you’ve built:

Your deployment is just the beginning. Explore the implementation guides, customize the design system, and let’s build something amazing.