How to Fix Firebase Hosting Route 404: SPA, SSG, Subpath — All Cases

After deploying to Firebase Hosting, the homepage works but /about and other subpages return 404? Your firebase.json rewrites are misconfigured. Fixes for SPA, SSG, static multipage, Astro, and Next.js.

You deploy to Firebase Hosting. The homepage opens fine. You navigate to /about or refresh /blog/some-post and get a 404. Welcome to one of the most common Firebase Hosting gotchas. The problem is almost never Firebase itself — it’s firebase.json rewrites being missing or wrong.

Identify which case you’re in (5 seconds)

  • Home OK, refresh subpage → 404 → SPA fallback rewrite missing
  • Direct subpage works, refresh works, only specific URLs 404 → SSG output missing those static files
  • All paths under a subpath (e.g., /en/) are 404 → that subpath has no index.html
  • First deploy, all paths 404 → wrong public directory

SPA: must rewrite to /index.html

Vue / React / Vite SPAs handle routing in the browser (React Router / Vue Router). When you hit /about directly, Firebase looks for /about.html, doesn’t find one, returns 404.

Fix: add a catch-all rewrite to index.html:

{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  }
}

Note: "public" must match your actual build output directory. Vite/Vue/React typically dist; Create React App is build.

SSG: Astro / Next.js static export / Hugo

SSG generates each page’s .html file, e.g., /about/index.html. Usually no SPA fallback needed, but watch for:

Trap 1: Astro trailing-slash inconsistency

Astro emits /about/index.html. Visiting /about (no trailing slash) sometimes 404s. Firebase usually auto-redirects with a 301, but edge cases fail.

Fix — in astro.config.mjs:

export default defineConfig({
  trailingSlash: 'always',
  build: { format: 'directory' }
});

Make sure your build output uses the subdir/index.html pattern.

Trap 2: Next.js static export path mismatch

next build && next export outputs out/about.html by default — not out/about/index.html. Firebase looks for /about as a directory, fails, 404.

Fix — in next.config.js:

module.exports = {
  output: 'export',
  trailingSlash: true,
};

Trap 3: Dynamic routes never generated

[slug].tsx (Next) or [slug].astro (Astro) need all routes enumerated at build time, or those URLs simply don’t exist in the SSG output.

Fix:

  • Astro: export getStaticPaths()
  • Next.js: export getStaticPaths and return all paths

Static multipage sites (one HTML per page)

Simplest case. Usually no rewrites needed:

{
  "hosting": {
    "public": "site",
    "cleanUrls": true,
    "trailingSlash": true
  }
}

cleanUrls lets /about.html be served at /about; trailingSlash enforces consistency.

Subpath / multilingual deployment

For /zh/ and /en/ to both work:

  • public/zh/index.html and public/en/index.html must both exist
  • If they’re SPA per-language, add a fallback per subpath:
"rewrites": [
  { "source": "/zh/**", "destination": "/zh/index.html" },
  { "source": "/en/**", "destination": "/en/index.html" },
  { "source": "**", "destination": "/index.html" }
]

Wrong deploy directory (silly but common)

  • firebase.json says "public": "public" but your framework outputs dist/
  • After deploy, Firebase only sees a placeholder public/ — every real page 404s

Fix: change "public" to your actual build output directory.

Pre-deploy checklist

In order:

  1. firebase.json’s "public" matches your real build output directory
  2. That directory actually contains index.html + subpage files
  3. SPA project: catch-all rewrites to /index.html present
  4. SSG project: trailingSlash consistent everywhere
  5. Multilingual subpaths: each subpath has its own index.html
  6. After firebase deploy --only hosting, “X files deployed” should be a reasonable count

Shortest fix path

In hit-rate order:

  1. Add SPA fallback rewrite → fixes ~80% of subpage 404s
  2. Inspect the build directory; confirm files exist → fixes ~15%
  3. Verify the public field → fixes the worst 5%

When it isn’t your fault

  • Firebase Hosting global incident (check status.firebase.google.com)
  • CDN cache lag — first 5 minutes after deploy, 404s may be cached
  • Custom domain DNS not propagated yet (24–48 hours)

If the 404 originates from a rewrite-to-function path instead, the cause is usually a different layer — see rewrites not firing for rule-order / shadowing issues, and Firebase function not found for region or name mismatches between firebase.json and the deployed function.

Easy misjudgments

  • “SSG doesn’t need rewrite” → mostly true, but trailing-slash misconfigs still 404
  • “Successful deploy = working” → only means files uploaded; routes may still be wrong
  • “Works in emulator → works in production”firebase serve differs slightly from real Hosting
  • “Cache is fine” → Firebase caches HTML by default; hard-refresh might not bypass; use an incognito window

Prevention

  • First deploy → use firebase hosting:channel:deploy preview to preview
  • Any change to firebase.json → run firebase serve --only hosting locally first
  • Don’t name the build directory public/ — it collides with Firebase defaults
  • Add a CI step that curl-checks key paths return 200 after deploy

FAQ

Q: Astro deployed to Firebase keeps 404-ing — what now? A: 80% of the time it’s a trailingSlash inconsistency between astro.config.mjs and firebase.json’s cleanUrls / trailingSlash. Align both to “always” or “never.”

Q: Does Next.js have to be SSG-exported for Firebase Hosting? A: Pure Hosting only supports static. For SSR, use Firebase Functions / Cloud Run. Vercel is simpler for Next.js.

Q: Difference between rewrite and redirect? A: Rewrite — URL stays the same, internally serves another file. Redirect — browser navigates to a new URL. SPA fallback uses rewrite.

Q: After deploy I still see the old site? A: Browser cache or Firebase CDN cache. Use incognito, hard refresh (Cmd+Shift+R), or append ?v=1 to bust the cache.

Q: Multiple hosting sites in one Firebase project? A: Use an array in firebase.json — one entry per site. Deploy with firebase deploy --only hosting:siteId.

Decision checklist

  • If the error started right after a change, roll back or isolate that change before trying unrelated fixes.
  • If the error happens only in production, compare environment variables, build output, cache, permissions, and platform settings.
  • If the error happens only for one account or browser, test permissions, cookies, extensions, quota, and regional availability.
  • If two fixes seem possible, choose the one that is easiest to verify and easiest to undo first.

When to stop debugging

Stop and escalate when you cannot reproduce the issue, when logs contradict the UI, when billing or account security is involved, or when every fix requires production access you do not control. At that point, package the exact error, timestamp, project ID, reproduction steps, screenshots, and recent changes before asking support or another engineer. Good escalation notes often solve the problem faster than another hour of guessing.

Diagnostic flow

  1. Reproduce the issue once and write down the exact path. If you cannot reproduce it, collect more evidence before changing settings.
  2. Check scope: one user or everyone, one browser or all browsers, local only or production only, new content only or old content too.
  3. Check the last change first. Most troubleshooting work is not about finding a mysterious root cause; it is about identifying which recent change created the mismatch.
  4. Split the system in two: input vs output, local vs hosted, account vs project, source file vs generated file, prompt vs model. Test which side still fails.
  5. Apply the smallest reversible fix. Avoid changes that touch DNS, permissions, billing, deployment, and code at the same time.
  6. Verify the original reproduction path and one nearby path, then write down what fixed it.

Minimal reproduction template

Issue:
- [exact error or broken behavior]

Where it happens:
- URL / tool / project:
- Account:
- Environment: local / preview / production
- Browser / device:

Steps to reproduce:
1.
2.
3.

Expected:
- 

Actual:
- 

Recent changes:
- Code:
- Config:
- DNS / permissions / billing:
- Prompt / model / uploaded files:

Evidence:
- Screenshot:
- Console error:
- Server or platform log:

False fixes to avoid

  • Clearing cache without checking whether the underlying file, permission, route, or setting is correct.
  • Reinstalling packages when the error is actually caused by environment variables, credentials, quota, or platform config.
  • Changing several unrelated settings at once, then not knowing which one mattered.
  • Copying a fix from another framework or platform without checking whether the routing, build output, or auth model is the same.
  • Treating a temporary platform outage as your own bug before checking status pages and recent reports.

Tags: #Firebase #Hosting #Route 404 #Debug