Deploy an Astro Site to Firebase Hosting (2026)

Deploy a static Astro site to Firebase Hosting the right way: the firebase.json config, cache headers, clean URLs, and the SPA-rewrite trap that breaks your 404s.

Firebase Hosting is a cheap, reliable home for a static Astro site: free SSL, a global CDN, and a free tier that is fine for commercial use. The deploy itself is two commands. The whole game is in firebase.json — get the rewrites and cache headers right once, and you never touch it again.

TL;DR

  • Set the public directory to dist and answer No to the single-page-app rewrite prompt. Astro ships real HTML routes, not an SPA shell — accepting the SPA rewrite makes every 404 return HTTP 200.
  • Add three cache rules in firebase.json: a one-year immutable cache for /_astro/** hashed assets, 30 days for images and fonts, and a short cache for HTML.
  • Set site: in astro.config.mjs or your sitemap and canonical tags break.
  • The Spark (free) plan gives you 10 GB stored and 360 MB/day of transfer, custom domains, and auto-issued SSL at no cost — and it is allowed for commercial sites.

When Firebase Hosting is the right call

Firebase Hosting fits if any of these are true:

  • You want a predictable bill and a free tier that genuinely covers a small site.
  • You already use Firestore, Auth, or Cloud Storage and want one console and one CLI.
  • Your Astro project is static output (the default) — no server-side rendering required.
  • You are comfortable running one CLI deploy step (or wiring it into a GitHub Action).

Pick Vercel or Cloudflare Pages instead when you lean on SSR, edge middleware, or want zero-config Git deploys with preview URLs out of the box. See Firebase vs Vercel for the full trade-off.

Plans and limits (as of June 2026)

Spark (free)Blaze (pay-as-you-go)
Price$0Usage-based, billed monthly
Hosting storage10 GB10 GB free, then ~$0.026/GB
Data transfer360 MB/day360 MB/day free, then ~$0.15/GB
Custom domainsIncludedIncluded
Free SSLYes (auto-issued)Yes (auto-issued)
Commercial useAllowedAllowed

A typical content site of a few hundred pages stays inside the free Spark quota indefinitely. Move to Blaze only when you need other paid Firebase products (Cloud Functions for SSR, for example) or you outgrow 360 MB/day of transfer. Confirm current numbers on the Firebase pricing page before you commit — these are usage quotas, not a flat plan.

Step by step

1. Install the CLI and log in

You need Firebase CLI 12.1.0 or newer for framework-aware hosting.

npm install -g firebase-tools
firebase --version   # expect 12.1.0 or higher
firebase login

2. Initialize hosting — and say No to SPA rewrites

Run this in your Astro project root. The single answer that matters: when asked to configure as a single-page app, answer No. Astro emits one HTML file per route, so an SPA rewrite would serve index.html for every path — including paths that should 404.

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)

3. Write the firebase.json that avoids every common pitfall

{
  "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" }
        ]
      }
    ]
  }
}

Why each line earns its place:

  • cleanUrls: true drops the .html extension and 301-redirects /about.html to /about. trailingSlash: true makes Firebase add the trailing slash, which must match trailingSlash: 'always' in your Astro config or you will bounce visitors through redirect loops.
  • /_astro/** holds Astro’s content-hashed JS and CSS. The filename changes on every build, so a one-year immutable cache is safe and gives repeat visitors instant loads.
  • Images and fonts get 30 days (max-age=2592000).
  • HTML gets a short browser cache (5 minutes) with a longer shared CDN cache (s-maxage=3600), so a re-deploy is visible quickly while the CDN still absorbs traffic.

4. Set site: in astro.config.mjs

Sitemap generation and canonical URLs both read from site:. Skip it and your sitemap is broken on the day you launch.

import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://yourdomain.com',
  trailingSlash: 'always',
  build: { format: 'directory' },
  integrations: [sitemap()],
});

5. Build and deploy

npm run build
firebase deploy --only hosting
# Hosting URL: https://your-project.web.app

6. Add your custom domain

In Firebase Console go to Hosting, then Add custom domain, and follow the DNS prompts — usually two A records pointing at Firebase’s IPs. SSL is issued automatically once DNS propagates, typically within 1-24 hours. Do not switch production DNS before the certificate shows as provisioned, or visitors will hit certificate warnings during the gap.

7. Verify the cache headers in production

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

8. Confirm your 404 actually returns 404

Visit a deliberately broken URL such as /this-does-not-exist/ and check the status. If it returns HTTP 200 with your homepage rendered, the SPA rewrite is still in play — go back and remove it from firebase.json. Google treats soft 404s as thin content, so this check is worth doing on every deploy.

curl -sI https://yourdomain.com/this-does-not-exist/ | head -1
# HTTP/2 404

How Firebase resolves a request (rewrite order)

When a rewrite you do want silently does nothing, the cause is almost always priority order. Firebase Hosting applies rules in this fixed order, and within redirects or rewrites only the first matching rule fires:

  1. Reserved /__/* namespaces (Firebase internals)
  2. Configured redirects
  3. Exact-match static content (a real file in dist/)
  4. Configured rewrites
  5. Your custom 404.html
  6. The default 404

The practical takeaway: a real file always beats a rewrite, and rule order inside each block matters. If a rewrite is being shadowed by a static file or an earlier rule, see Firebase rewrites not firing.

Common pitfalls

  • Accepting the default SPA rewrite. Your 404 pages return 200 and Google indexes them as thin pages. This is the single most common Astro-on-Firebase mistake.
  • Forgetting cache headers. Firebase’s default cache is conservative, so repeat visits re-download assets that never change.
  • DNS before SSL. Pointing production DNS at Firebase before the certificate is provisioned shows visitors a security warning until propagation completes.
  • Expecting SSR from plain Hosting. Static Hosting only serves files. SSR needs the Astro Node adapter plus a Cloud Function or Cloud Run backend, which requires the Blaze plan. If a wired-up function returns “function not found”, it is usually a region or name mismatch, not a deploy failure (Firebase function not found).
  • Skipping site: in astro.config.mjs. Sitemap and canonical tags break.

FAQ

  • Is Firebase Hosting really free?: Yes for small sites. The Spark plan includes 10 GB of stored data, 360 MB/day of transfer, custom domains, and auto-issued SSL at no cost, and it is allowed for commercial use. Beyond those quotas you move to Blaze, which is usage-based and very cheap for static content (roughly $0.15/GB of overflow transfer as of June 2026).
  • How do I run Astro SSR on Firebase?: Add the Astro Node adapter and serve it from a Cloud Function or Cloud Run instance, which requires the Blaze plan. Most content and marketing sites do not need this — static output is faster and free.
  • Can I deploy from GitHub automatically?: Yes. Run firebase init hosting:github to generate a GitHub Action that builds and deploys on push, including preview channels for pull requests. It is easier and more reliable than manual deploys.
  • How do I roll back a bad deploy?: Firebase keeps a release history. List versions with firebase hosting:versions:list, clone the good one with firebase hosting:versions:clone, then firebase deploy --only hosting. Or open Hosting in the Console, hover the previous release, and choose Roll back. Both take seconds.
  • Do I need both cleanUrls and trailingSlash?: Use trailingSlash: true together with trailingSlash: 'always' in Astro so the two layers agree. Mismatched trailing-slash settings cause redirect loops or duplicate URLs that split your SEO signals.

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