Deploying an Astro site on Vercel: the 10-minute path

Ship an Astro site on Vercel with the exact astro.config.mjs, vercel.json, CLI commands, and SSL verification — no detours.

Astro on Vercel is genuinely a 10-minute setup if you do not get distracted by every checkbox. This walkthrough is the shortest path that still gets you HTTPS, a custom domain, image optimization, and preview deploys — with the exact astro.config.mjs, vercel.json, and CLI commands.

Background

Vercel detects Astro automatically and uses sensible defaults. The two settings that actually matter are output mode (static vs server) and the adapter (none for static, @astrojs/vercel for server). Most content sites should stay static. Everything else Vercel handles for you.

Before you start

  • Repo on GitHub/GitLab/Bitbucket; Vercel account linked.
  • Node version pinned in package.json.
  • Custom domain ready (optional but recommended).

Step by step

  1. Pin the basics in astro.config.mjs:
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
import mdx from '@astrojs/mdx';

export default defineConfig({
  site: 'https://yourdomain.com',           // required for sitemap + canonical
  trailingSlash: 'always',
  build: { format: 'directory' },
  output: 'static',                         // for content sites
  integrations: [mdx(), sitemap()],
});
  1. Optional vercel.json for cache headers, redirects, and clean URLs:
{
  "cleanUrls": true,
  "trailingSlash": true,
  "redirects": [
    { "source": "/blog/(.*)", "destination": "/articles/$1", "permanent": true }
  ],
  "headers": [
    {
      "source": "/_astro/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
      ]
    }
  ]
}
  1. Pin Node in package.json to avoid version surprises:
{
  "engines": { "node": "20.x" },
  "scripts": {
    "build": "astro build",
    "preview": "astro preview"
  }
}
  1. Push the repo + import on Vercel. Either UI flow (Add New → Project → import) or CLI:
npm install -g vercel
vercel link                 # link the local repo to a Vercel project
vercel --prod               # deploy to production
# Production: https://your-project.vercel.app
  1. For SSR (rare on content sites), add the Vercel adapter:
npm install @astrojs/vercel
// astro.config.mjs
import vercel from '@astrojs/vercel/serverless';

export default defineConfig({
  // ...
  output: 'server',
  adapter: vercel(),
});

Skip this section if you are static.

  1. Add environment variables for Production and Preview:
vercel env add SITE_URL production
# https://yourdomain.com
vercel env add OPENAI_API_KEY production
# paste secret

In the UI: Project → Settings → Environment Variables → check Production + Preview boxes for each variable that needs to exist in both.

  1. Verify build output + first deploy:
npm run build
ls dist/                                    # confirm sitemap.xml, index.html, _astro/
vercel --prod

Then visit https://your-project.vercel.app and click around. Test:

curl -sI https://your-project.vercel.app/ | grep -i cache-control
curl -sI https://your-project.vercel.app/sitemap-index.xml | head -1
  1. Add the custom domain. Settings → Domains → add yourdomain.com. Follow the DNS prompts (typically apex A 76.76.21.21 and CNAME www → cname.vercel-dns.com). SSL provisions automatically; usually < 1 hour.

Implementation checklist

  • astro.config.mjs sets site, trailingSlash, and a fixed output mode.
  • package.json pins Node via engines.node.
  • Environment variables added for Production AND Preview.
  • vercel.json (if used) is consistent with astro.config.mjs on trailing slash / cleanUrls.
  • Custom domain has green check, SSL valid.

After-launch verification

  • curl -sI returns 200 on the custom domain.
  • dist/sitemap-index.xml is reachable at production URL.
  • Search Console “Submitted” matches the sitemap entry count after first crawl.
  • Lighthouse SEO = 100 on at least one sample article.

Common pitfalls

  • Forgetting to set site in astro.config.mjs — sitemap and canonical URLs end up with localhost or the preview URL.
  • Adding the @astrojs/vercel adapter when you do not need SSR — slower builds, no benefit.
  • Leaving environment variables in .env and never adding them to Vercel — production fails with missing-var errors.
  • Setting build command to npm run build when vercel.json overrides it — check which one wins.
  • Trying to deploy node_modules accidentally — .vercelignore or .gitignore should handle it.
  • cleanUrls: true in vercel.json paired with trailingSlash: 'always' in Astro — generates unnecessary 308s. Pick consistently.

FAQ

  • Do I need the @astrojs/vercel adapter?: Only if you set output: 'server' or 'hybrid'. For a fully static site, the adapter is not needed.
  • Will Vercel optimize my images automatically?: Yes, via Astro’s built-in <Image> component on a deployment, or via @astrojs/vercel’s image service if you opt in.
  • How do preview deploys work for Astro?: Every git push (any branch) gets a preview URL. Pull requests get a comment with the URL. They are noindex by default.
  • What if my build fails on Vercel but works locally?: Most likely Node version or missing env var. Lock Node version in package.json engines field; check Vercel build logs.
  • Can I deploy from the CLI without GitHub?: Yes — vercel --prod from the project root works without a git remote.

Tags: #Indie dev #Vercel #Hosting #Astro #Getting started #Workflow