Deploying an Astro Site to Firebase Hosting

Step-by-step on deploying a static Astro site to Firebase Hosting, with the configuration that avoids the common pitfalls in 2026.

Firebase Hosting is a strong, cheap option for static Astro sites. The deploy is short — the gotchas are in the config.

Background

Firebase Hosting offers free SSL, a global CDN, and predictable pricing for static sites. Astro outputs to dist/ and Firebase serves it. The friction is around firebase.json rewrites, cache headers, and how Firebase resolves clean URLs for routes like /articles/slug/.

How to tell

  • You want predictable pricing and a free tier that covers small sites.
  • You already use other Firebase services and want one platform.
  • You don’t need server-side rendering — static output is enough.
  • You’re comfortable with a CLI deploy step.

Quick verdict

Use Firebase Hosting for static Astro sites when cost predictability and Firebase ecosystem fit matter. Pick Vercel when DX and zero-config matter more.

Step by step

  1. Install the Firebase CLI globally and authenticate:
npm install -g firebase-tools
firebase login
  1. In your Astro project root, initialize hosting. When prompted, set the public directory to dist and answer No to “single-page app rewrites” — Astro outputs real routes, not an SPA shell:
firebase init hosting
# ? What do you want to use as your public directory? dist
# ? Configure as a single-page app (rewrite all urls to /index.html)? No
# ? Set up automatic builds and deploys with GitHub? (your choice)
  1. Edit firebase.json to handle clean URLs, cache headers, and a real 404. This is the config that avoids the common pitfalls:
{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "cleanUrls": true,
    "trailingSlash": true,
    "headers": [
      {
        "source": "/_astro/**",
        "headers": [
          { "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }
        ]
      },
      {
        "source": "**/*.@(jpg|jpeg|png|webp|avif|svg|woff2)",
        "headers": [
          { "key": "Cache-Control", "value": "public, max-age=2592000" }
        ]
      },
      {
        "source": "**/*.html",
        "headers": [
          { "key": "Cache-Control", "value": "public, max-age=300, s-maxage=3600" }
        ]
      }
    ]
  }
}
  1. Make sure astro.config.mjs has site: set — sitemap and canonical generation depend on it:
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://yourdomain.com',
  trailingSlash: 'always',
  build: { format: 'directory' },
  integrations: [sitemap()],
});
  1. Build and deploy:
npm run build
firebase deploy --only hosting
# Hosting URL: https://your-project.web.app
  1. In Firebase Console → Hosting → Add custom domain, follow the DNS prompts (typically two A records pointing at Firebase’s IPs). SSL is issued automatically once DNS propagates — usually 1-24 hours.

  2. Verify cache headers from production with curl -I:

curl -sI https://yourdomain.com/ | grep -i cache-control
# cache-control: public, max-age=300, s-maxage=3600

curl -sI https://yourdomain.com/_astro/index.abc123.css | grep -i cache-control
# cache-control: public, max-age=31536000, immutable
  1. Visit a deliberately broken URL (/this-does-not-exist/) and confirm the response is HTTP 404 — not 200 with the homepage rendered. If you see 200, your rewrites are still acting like an SPA.

Common pitfalls

  • Accepting the default SPA rewrite — your 404 pages will return 200 and confuse Google. If a rewrite you do want is silently not firing, the cause is almost always rule order or a shadowing static file (rewrites not firing).
  • Forgetting cache headers; the default is conservative and slows repeat visits.
  • Pointing DNS before Firebase has provisioned SSL — visitors see warnings until propagation.
  • Trying to use SSR Astro on Firebase Hosting without Cloud Functions — only static output is served. If you do wire a function in and it returns “function not found”, that is usually a region or name mismatch, not a deploy failure (Firebase function not found).
  • Skipping the astro.config.mjs site: setting — your sitemap will be broken.

Who this is for

Indies on the Firebase ecosystem deploying static Astro sites, or anyone who wants predictable pricing.

When to skip this

Teams who need SSR-heavy Astro setups; consider Vercel or Cloudflare Pages with their server runtimes instead.

FAQ

  • Is Firebase Hosting really free?: It has a generous free tier (1 GB storage, 10 GB/month transfer). Beyond that, the Blaze plan is pay-as-you-go and very cheap for static sites.
  • How do I run Astro SSR on Firebase?: Use Cloud Functions or Cloud Run as the backend and the Astro Node adapter. Most content sites do not need this.
  • Can I deploy from GitHub automatically?: Yes, via firebase init hosting:github which sets up a GitHub Action on push. Easier than manual deploys.
  • How do I roll back a bad deploy?: Firebase Hosting keeps deploy history. Use firebase hosting:clone or roll back in the console — both are quick.

Tags: #Indie dev #Astro #Firebase #Hosting #SSL #DNS