Files
SlipItIn/.agents/skills/astro-sites-manager/references/deployment.md
Tim Krampitz ffb017568b
Some checks failed
Build / build (push) Successful in 2m0s
OpenWiki Update / update (push) Has been cancelled
Move skills to .agent directory
2026-10-04 22:13:19 +02:00

8.0 KiB

Deployment Guide

Astro 7 deployment across all major platforms. Covers adapters, route caching, and platform-specific gotchas.


1. General Build

astro build    # output in dist/
astro preview  # test production build locally

Key config in astro.config.mjs:

import { defineConfig } from 'astro/config';

export default defineConfig({
  site: 'https://example.com',
  base: '/',
  trailingSlash: 'never', // 'always' | 'never' | 'ignore'
});
  • site — full production URL (required for sitemaps, canonical URLs, RSS)
  • base — subpath when deploying to a subdirectory (e.g., /docs)
  • trailingSlash — MUST match hosting platform expectations to avoid redirect loops

2. Cloudflare Pages

Adapter: @astrojs/cloudflare

npx astro add cloudflare
// astro.config.mjs
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';

export default defineConfig({
  output: 'server',
  adapter: cloudflare(),
});

Route Caching (private beta):

// astro.config.mjs
import { cacheCloudflare } from '@astrojs/cloudflare/cache';

export default defineConfig({
  output: 'server',
  adapter: cloudflare(),
  experimental: {
    serverIslands: true,
  },
  routeCache: cacheCloudflare(),
});

Deploy:

  • Connect git repo in Cloudflare Dashboard → Pages → Create a project
  • Build command: astro build
  • Build output directory: dist
  • Node.js compatibility flag is set automatically by the adapter

3. Vercel

Adapter: @astrojs/vercel

npx astro add vercel
// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel';
import { cacheVercel } from '@astrojs/vercel/cache';

export default defineConfig({
  output: 'server',
  adapter: vercel(),
  routeCache: cacheVercel(),
});

ISR via routeRules:

// astro.config.mjs
export default defineConfig({
  output: 'server',
  adapter: vercel({
    isr: true, // enable ISR globally
  }),
  routeCache: cacheVercel({
    routeRules: {
      '/blog/**': { revalidate: 60 },   // revalidate every 60s
      '/static/**': { prerender: true }, // fully static at build
    },
  }),
});

Deploy:

  • Connect repo via Vercel Dashboard or vercel CLI
  • Framework preset: Astro (auto-detected)

4. Netlify

Adapter: @astrojs/netlify

npx astro add netlify
// astro.config.mjs
import { defineConfig } from 'astro/config';
import netlify from '@astrojs/netlify';
import { cacheNetlify } from '@astrojs/netlify/cache';

export default defineConfig({
  output: 'server',
  adapter: netlify(),
  routeCache: cacheNetlify(),
});

Deploy:

  • Connect repo in Netlify Dashboard
  • Build command: astro build
  • Publish directory: dist
  • Functions auto-detected from adapter output

5. Firebase Hosting

Static only — no adapter needed for SSG output.

// astro.config.mjs
export default defineConfig({
  output: 'static',
  trailingSlash: 'never', // CRITICAL: must match Firebase config
});

firebase.json:

{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "trailingSlash": false,
    "rewrites": [
      { "source": "**", "destination": "/404.html" }
    ]
  }
}

Deploy:

astro build
firebase deploy --only hosting

CRITICAL: trailingSlash Alignment

Mismatch between Firebase and Astro causes infinite redirect loops.

Firebase trailingSlash Astro trailingSlash Result
false 'never' ✅ Works
true 'always' ✅ Works
false 'always' ❌ Redirect loop
true 'never' ❌ Redirect loop

6. GitHub Pages

Static output only — no adapter needed.

// astro.config.mjs
export default defineConfig({
  site: 'https://username.github.io',
  base: '/repo-name', // omit for username.github.io root
  output: 'static',
});

GitHub Actions workflow (.github/workflows/deploy.yml):

name: Deploy to GitHub Pages

on:
  push:
    branches: [main]

permissions:
  contents: read
  pages: write
  id-token: write

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-pages-artifact@v3
        with:
          path: dist

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - id: deployment
        uses: actions/deploy-pages@v4

7. Docker / Self-Hosted (Coolify)

Adapter: @astrojs/node

npx astro add node
// astro.config.mjs
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';

export default defineConfig({
  output: 'server',
  adapter: node({
    mode: 'standalone',
  }),
  server: {
    host: '0.0.0.0', // REQUIRED for Docker
    port: 4321,
  },
});

Dockerfile:

FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:22-alpine AS runtime
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./
ENV HOST=0.0.0.0
ENV PORT=4321
EXPOSE 4321
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://localhost:4321/api/health || exit 1
CMD ["node", "./dist/server/entry.mjs"]

Health check endpoint (src/pages/api/health.ts):

import type { APIRoute } from 'astro';

export const GET: APIRoute = () => {
  return new Response(JSON.stringify({ status: 'ok' }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  });
};

Coolify: Set Dockerfile build pack, expose port 4321, configure health check to /api/health.


8. Azure Static Web Apps

Works with static output (SSG). For SSR, use Azure Functions integration.

GitHub Actions workflow (.github/workflows/azure-swa.yml):

name: Azure Static Web Apps

on:
  push:
    branches: [main]

jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run build
      - uses: Azure/static-web-apps-deploy@v1
        with:
          azure_static_web_apps_api_token: ${{ secrets.AZURE_SWA_TOKEN }}
          repo_token: ${{ secrets.GITHUB_TOKEN }}
          action: upload
          app_location: /
          output_location: dist
          skip_app_build: true

The skip_app_build: true pattern means we build ourselves (for control over Node version and env vars) and only upload the output.

staticwebapp.config.json:

{
  "navigationFallback": {
    "rewrite": "/404.html"
  },
  "globalHeaders": {
    "X-Frame-Options": "DENY",
    "X-Content-Type-Options": "nosniff"
  },
  "routes": [
    {
      "route": "/api/*",
      "allowedRoles": ["authenticated"]
    }
  ]
}

For private registry auth (private npm packages):

      - run: |
          echo "//npm.pkg.github.com/:_authToken=${{ secrets.NPM_TOKEN }}" >> .npmrc
      - run: npm ci

9. Pre-Deploy Checklist

  • astro build exits 0
  • astro check reports no errors
  • astro preview works correctly (test production build locally)
  • Images use <Image/> component or are in public/
  • SEO metadata present on all pages (title, description, og tags)
  • src/pages/404.astro exists
  • Environment variables set on target platform
  • trailingSlash matches hosting platform expectations
  • Sitemap generating correctly (@astrojs/sitemap)
  • RSS feed working if applicable (@astrojs/rss)
  • Route caching configured for SSR pages (platform-specific cache helper)
  • robots.txt present and correct
  • HTTPS redirect configured on platform
  • Custom domain DNS configured and propagated