Rewrites are the most misused feature in Firebase Hosting. The right rewrite makes your SPA work and dynamic routes hit a Cloud Function. The wrong one silently flattens your site’s SEO or returns blank pages. This article walks through the four real use cases and the trap each one hides.
Background
A rewrite in firebase.json tells the CDN: “when the user requests path X, serve content Y.” Y can be a different static file, a Cloud Function, a Cloud Run service, or another Hosting site. Rewrites run before the 404 fallback, so they cover every URL that does not match a real file. That power is why people copy-paste rewrite blocks they do not understand.
How to tell
- You have a SPA and want
/anythingto load/index.html. - You want
/api/*to hit a Cloud Function or Cloud Run service. - You want to alias one path to another (e.g.
/blog/*to/articles/*). - You want to serve content from another Firebase Hosting site at a path.
Quick verdict
Use rewrites only for the four cases above. For redirects (URL changes), use the redirects block instead. For most multi-page static sites, you do not need any rewrites at all.
Step by step
-
Decide which case you actually have: SPA, dynamic backend, alias, or multi-site. Only one of these — mixing is where bugs start.
-
SPA pattern. Use only on apps with client-side routing (React Router, Vue Router). Never on a multi-page static site:
{
"hosting": {
"public": "dist",
"rewrites": [
{ "source": "**", "destination": "/index.html" }
]
}
}
- Dynamic backend pattern. Scope to a prefix; never wildcard
**to a function. Example with both a Cloud Function and a Cloud Run service:
{
"hosting": {
"public": "dist",
"rewrites": [
{ "source": "/api/v1/**", "function": "api", "region": "us-central1" },
{ "source": "/og/**", "run": { "serviceId": "og-image", "region": "us-west1" } }
]
}
}
Match Cloud Function’s deployed region exactly — a region mismatch is the #1 cause of “function not found” after a successful deploy.
- Multi-site pattern. Serve another Hosting site under a path — useful for status pages or docs subsites:
{
"hosting": {
"site": "marketing",
"public": "dist",
"rewrites": [
{ "source": "/docs/**", "site": "docs" }
]
}
}
- For renaming/moving URLs use redirects, not rewrites — search engines need the 301 to update their index:
{
"hosting": {
"redirects": [
{ "source": "/blog/:slug", "destination": "/articles/:slug", "type": 301 },
{ "source": "/old/**", "destination": "/new/:0", "type": 301 }
]
}
}
- Deploy and verify in incognito with
curl:
firebase deploy --only hosting
# Static file still served directly
curl -sI https://yourdomain.com/about.html | head -1
# HTTP/2 200
# API path hits the function
curl -sI https://yourdomain.com/api/v1/health | head -1
# HTTP/2 200
# Old URL redirects with 301
curl -sI https://yourdomain.com/blog/foo | grep -E 'HTTP|location'
# HTTP/2 301
# location: https://yourdomain.com/articles/foo
- Watch logs for the first 24 hours. If crawlers hit your rewrite for static assets (
*.png,*.css), your rule is too broad:
firebase functions:log --only api --lines 200 | grep -E '\.(png|css|js|ico)' | head
# any output = your wildcard is matching too much
Common pitfalls
- Wildcard rewrite to
/index.htmlon a multi-page site — every URL becomes the same HTML, killing SEO. - Sending all traffic through a Cloud Function — slow, expensive, and unnecessary for static assets.
- Using a rewrite when a redirect was the right tool. Rewrite keeps the URL; redirect changes it.
- Forgetting that rewrites do not change the URL bar — search engines see the original path.
- Missing
cleanUrlsinteraction — ifcleanUrls: true,/about.htmlis served at/about, and your rewrite may not match.
Who this is for
SPAs with client-side routing, sites with a dynamic API on Cloud Functions / Run, and multi-site setups that share one domain.
When to skip this
Plain multi-page static sites, sites that need true URL changes (use redirects), and apps where most traffic should be SSR — pick a framework-native host.
FAQ
- What is the difference between rewrite and redirect?: Rewrite keeps the URL the user sees but serves different content. Redirect sends a 301/302 and the URL changes.
- Can I rewrite to a Cloud Function and pass query params?: Yes. Query strings are forwarded automatically. Path segments matched by
**are available via the request URL. - Does the order of rewrites matter?: Yes. Firebase matches the first rewrite whose
sourcematches. Put specific rules before catch-alls. When a rewrite seemingly does nothing in production, the cause is almost always order or a static file shadowing the source — walk through the diagnosis in rewrites not firing. - The rewrite deployed but the function returns 404 / “function not found”.: That is a function-region / name mismatch, not a rewrite bug — see Firebase function not found.
- Why does my SPA rewrite break my sitemap?: Because
**matches/sitemap.xmltoo. Exclude it: scope your wildcard or put a more specific rule for/sitemap*first.