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 usingexport 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:
| Feature | Used by the site? | Needs the adapter? |
|---|---|---|
| On-demand rendered routes | No (0 routes) | Yes |
| Server islands | No | Yes |
Vercel Image Optimization (imageService: true) | Yes, 900 pages | Yes |
| Web Analytics / Speed Insights | Yes | No (script tag works without) |
| ISR | No | Yes |
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.
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.
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
| Measure | With adapter | Fully static | Tool |
|---|---|---|---|
| Functions in deployment | 1 | 0 | Vercel dashboard |
| Image optimisation usage / month | ~38,000 source images | 0 | Vercel usage |
| Cold build | 1 min 50 s | 4 min 00 s | Vercel build log |
| Warm build | 1 min 45 s | 2 min 05 s | Vercel build log |
| LCP p75, mobile | 1.6 s | 1.6 s | Speed Insights |
| Image bytes per page, median | 290 KB | 270 KB | DevTools |
| Monthly cost (Pro plan, this site) | base + image usage | base only | Invoice |
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
redirectsfor static output. Meta-refresh pages; usevercel.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.
Related
- Parent: Netlify vs Vercel Deployment Strategies — platform choices.
- Vercel ISR vs Static Generation for SSGs — when on-demand rendering earns the adapter.
- Cache-Control Headers on Vercel for Static Sites — header rules in depth.
- Caching Processed Images Between Builds — keeping build-time images fast.
- Deploying Astro to Cloudflare Workers Static Assets — the same static output on another host.