Deploying Astro to Vercel as a Fully Static Site

Astro's documentation for Vercel leads with the @astrojs/vercel adapter, and many projects install it by default. For a content site that never renders on demand, that brings things the site does not need: serverless functions in the deployment, function invocations on the bill, an image service that resizes on request, and a deployment shape that is harder to move elsewhere. Vercel deploys Astro's static output perfectly well without any adapter — it detects the framework, runs astro build and serves dist/ as files.

This guide deploys a 900-page Astro 5 marketing and documentation site to Vercel as a fully static site, removes an adapter the site had been carrying, moves image processing back to build time, sets headers and redirects in vercel.json, and compares the result with the adapter version. It belongs to Netlify vs Vercel Deployment Strategies.

Prerequisites

  • An Astro site with output: 'static' (the default) and no routes using export const prerender = false.
  • A Vercel project connected to the repository, or the Vercel CLI for deploys from CI.

What the Adapter Was Doing

The site had added @astrojs/vercel during a trial of Vercel's image optimisation, and it stayed. An audit of the deployment showed:

FeatureUsed by the site?Needs the adapter?
On-demand rendered routesNo (0 routes)Yes
Server islandsNoYes
Vercel Image Optimization (imageService: true)Yes, 900 pagesYes
Web Analytics / Speed InsightsYesNo (script tag works without)
ISRNoYes

Only image optimisation used the adapter. It resized images per request at Vercel's edge, which counted toward the plan's image optimisation quota and produced a function in the deployment.

What the Vercel deployment contained with the adapter and as fully static output Two panels. With the adapter, the deployment had static files plus a serverless function, images resized on request through Vercel's image service, and image optimisation usage on the bill. Fully static, the deployment has only files, images resized at build time with sharp into static variants, and no function invocations or image optimisation usage. The deployment with and without the adapter With adapter static files + 1 function images resized per request image optimisation billed more moving parts Fully static static files only images resized at build no function invocations portable to any host
Readers get the same page either way; what changes is where the work happens and what is billed.

Step 1: Remove the Adapter

npm uninstall @astrojs/vercel
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
  site: 'https://example.com',
  trailingSlash: 'always',
  build: { format: 'directory' },
  image: { domains: ['images.example-cdn.com'] },
});

Without an adapter, astro build writes plain static files to dist/. Vercel's framework preset detects Astro and uses dist as the output directory automatically. build.format: 'directory' writes page/index.html, matching trailingSlash: 'always'.

Step 2: Images at Build Time

Astro's default image service, sharp, processes images during the build into hashed static files with srcset. Components did not change: <Image> and <Picture> work the same whether the service is Vercel's or sharp. The build got longer — 900 images into four widths and two formats added 2 minutes 10 seconds to a cold build — and caching Astro's asset cache between builds brings warm builds back. On Vercel's own build system, node_modules/.astro is preserved between builds for the same project, so warm builds processed only new images.

Remote images from the site's CMS CDN are listed in image.domains, so Astro downloads and processes them at build time too.

Step 3: Headers and Redirects in vercel.json

Without the adapter, headers and redirects for static output come from vercel.json:

{
  "trailingSlash": true,
  "headers": [
    { "source": "/_astro/(.*)", "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }] },
    { "source": "/(.*)", "headers": [
      { "key": "X-Content-Type-Options", "value": "nosniff" },
      { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }
    ] }
  ],
  "redirects": [
    { "source": "/docs/old-guide/", "destination": "/docs/new-guide/", "permanent": true }
  ]
}

Vercel's Astro preset already marks /_astro/ as immutable; the explicit rule documents it. Security headers are covered fully in Security Headers on Vercel for Static Sites, and redirects in Configuring Redirects on Vercel for Static Sites. Astro's own redirects config was removed: for static output it generates HTML pages with a meta refresh, which are slower and weaker for search than the real 308s from vercel.json.

Step 4: Analytics Without the Adapter

Vercel Web Analytics and Speed Insights work on static sites through their script packages, without the adapter:

---
import Analytics from '@vercel/analytics/astro';
import SpeedInsights from '@vercel/speed-insights/astro';
---
<Analytics /><SpeedInsights />

Step 5: Keep It Static

A CI check fails the build if the output ever gains a function — for example, if someone adds a route with prerender = false, which without an adapter is a build error in Astro anyway, or reinstalls the adapter:

test ! -d .vercel/output/functions || { echo "deployment contains functions"; exit 1; }

When building in CI with vercel build, the .vercel/output directory shows exactly what will deploy.

Pipeline for deploying Astro to Vercel as static output Five steps. Vercel detects Astro and runs astro build. Sharp processes images at build time using the restored asset cache. The output is plain files in dist. vercel.json applies headers and redirects. The deployment is files only, with no functions. The static deploy pipeline astro build preset detected Images via sharp cache restored dist/ files no adapter output vercel.json headers, redirects Static deployment 0 functions
The same dist folder can be deployed to any static host unchanged.

Preview Deployments and Environment Variables

Preview deployments work the same as with the adapter: every pull request gets a URL with the static build. Two details needed attention. site in the Astro config is the production URL, so canonical tags and sitemaps on previews point at production — correct for search engines, but the team also adds X-Robots-Tag: noindex for preview hosts through a vercel.json header rule matched on the host value, as in Fixing Preview Deploys Leaking Into Search Results. And public environment variables (PUBLIC_*) are read at build time and baked into the static output, so a variable changed in the Vercel dashboard only takes effect after a rebuild — unlike with on-demand rendering, where some values are read per request. A note in the README saved one confused afternoon.

When to Add the Adapter Back

The decision rule the team wrote down: add @astrojs/vercel the day a route genuinely needs per-request rendering — personalised content, form handling in the same project, or data fresher than a build — and mark only that route with prerender = false. Everything else stays prerendered. Until then, a fully static deployment is the simpler and cheaper thing to run, and the same dist/ can move to Cloudflare, Netlify or an S3 bucket without a code change.

Measured Impact

MeasureWith adapterFully staticTool
Functions in deployment10Vercel dashboard
Image optimisation usage / month~38,000 source images0Vercel usage
Cold build1 min 50 s4 min 00 sVercel build log
Warm build1 min 45 s2 min 05 sVercel build log
LCP p75, mobile1.6 s1.6 sSpeed Insights
Image bytes per page, median290 KB270 KBDevTools
Monthly cost (Pro plan, this site)base + image usagebase onlyInvoice
Cold build, warm build and image bytes with the adapter and fully static Paired bars. Cold build went from 110 seconds with the adapter to 240 seconds fully static. Warm build from 105 to 125 seconds. Median image bytes per page from 290 to 270 kilobytes. Build time and image bytes With adapter Fully static Cold build (s) 110 240 Warm build (s) 105 125 Image KB per page 290 270
Fully static builds do the image work themselves, so cold builds are slower; readers see slightly smaller images.

The trade is clear: builds do more work — slower cold builds, slightly slower warm ones — and the deployment does none. LCP was unchanged; image bytes dropped slightly because build-time AVIF settings were tuned for the site's screenshots.

The deployment summary in the Vercel dashboard now lists only static assets for every deploy.

Pitfalls & Rollback

  • Installing the adapter by default. Add it when a route needs on-demand rendering, not before.
  • Using Astro's redirects for static output. Meta-refresh pages; use vercel.json.
  • Uncached image processing. Restore the asset cache or builds slow down.
  • Assuming analytics needs the adapter. The script components work without it.
  • Rollback: reinstall the adapter and restore imageService: true; no component changes are needed.

Conclusion

An Astro content site does not need the Vercel adapter. Removing it made the deployment plain files with no functions, moved image work to build time with Astro's own service, and left headers and redirects to vercel.json. Readers saw no change in LCP; the site stopped paying for on-request image resizing and became portable to any static host, at the cost of about two minutes on cold builds.

FAQ

Do I need the Vercel adapter to deploy Astro to Vercel?

Not for a fully static site. Vercel detects Astro and deploys the dist folder as static files. The adapter is needed only for on-demand rendered routes, server islands, Vercel's image optimisation service, or other Vercel-specific features. Without it, the deployment has no functions at all.

Does Vercel's image optimisation work without the adapter?

No. Astro's built-in image service processes images at build time with sharp, which works without any adapter and produces static files. Vercel's image optimisation service, which resizes on request, requires the adapter. For most static sites, build-time processing is simpler and avoids per-image costs.

How do I add redirects and headers without the adapter?

With vercel.json at the project root. Its redirects and headers apply to the static deployment. Astro's own redirects config also works for static output by generating small HTML pages with meta refresh, but real HTTP redirects from vercel.json are better for search engines and speed.

Why would I deploy fully static instead of using the adapter?

Fewer moving parts: no functions to bill, cold-start or fail, a deployment that is just files, and an easy move to another static host later. Many content sites use none of the adapter's features, and adding it later is a small change if a route ever needs on-demand rendering.

Filed underHow-tosAstroVercel