Hosting
Firebase Hosting serves your static site + assets from a global CDN with zero config, free SSL, and instant rollbacks. Pair with Cloud Functions or Cloud Run for dynamic routes (rewrites) and you get a full-stack site without managing servers.
Init, deploy, rewrites, preview, multi-site
EXAMPLE
# 1) Install + init
npm install -g firebase-tools
firebase login
firebase init hosting
# • Pick existing project
# • Public directory: dist (or build, or .)
# • Configure as SPA? yes/no — yes rewrites all to /index.html
# • Set up GitHub Actions for deploys? optional
# 2) firebase.json — the main config
{
"hosting": {
"public": "dist",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"cleanUrls": true, # /about.html → /about
"trailingSlash": false,
"rewrites": [
{ "source": "/api/**", "function": "api" },
{ "source": "/legacy", "destination": "/legacy/index.html" },
{ "source": "**", "destination": "/index.html" } # SPA fallback
],
"redirects": [
{ "source": "/old", "destination": "/new", "type": 301 }
],
"headers": [
{
"source": "**/*.@(js|css)",
"headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }]
},
{
"source": "**/*.html",
"headers": [{ "key": "Cache-Control", "value": "no-cache" }]
},
{
"source": "**",
"headers": [
{ "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains" },
{ "key": "X-Content-Type-Options", "value": "nosniff" },
{ "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" }
]
}
],
"i18n": { "root": "/localized" } # optional locale-aware serving
}
}
# 3) Build + deploy
npm run build
firebase deploy --only hosting
# Output:
# Hosting URL: https://my-project.web.app
# https://my-project.firebaseapp.com
# 4) Custom domain
firebase hosting:channel:open my-project
# Console → Hosting → Add custom domain → follow DNS instructions
# Free SSL provisioned via Let's Encrypt within minutes.
# 5) Preview channels — share staging URLs from a PR
firebase hosting:channel:deploy pr-42 --expires 7d
# Produces a URL like https://my-project--pr-42-random.web.app
# Auto-expires; clean URLs without overwriting prod.
# 6) GitHub Actions integration
# Generated by 'firebase init hosting:github'
name: deploy
on:
push: { branches: [main] }
pull_request:
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci && npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: ${{ secrets.GITHUB_TOKEN }}
firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT_MY_PROJECT }}
projectId: my-project
channelId: ${{ github.event_name == 'push' && 'live' || format('pr-{0}', github.event.number) }}
# PRs get preview channel; pushes to main go to live.
# 7) Rewrites to Cloud Functions
# functions/index.ts
export const api = onRequest((req, res) => {
res.json({ hello: 'world' });
});
# firebase.json
"rewrites": [
{ "source": "/api/**", "function": "api" }
]
# 8) Rewrites to Cloud Run
"rewrites": [
{ "source": "/api/**", "run": { "serviceId": "my-api", "region": "us-central1" } }
]
# Hosting CDN caches public Cloud Run responses; great for dynamic + static blend.
# 9) Multi-site hosting
firebase hosting:sites:create marketing
firebase hosting:sites:create app
# firebase.json
"hosting": [
{ "target": "marketing", "public": "marketing/dist" },
{ "target": "app", "public": "app/dist" }
]
firebase target:apply hosting marketing marketing-site
firebase target:apply hosting app app-site
firebase deploy --only hosting:app
# 10) Rollback
firebase hosting:rollback
# Console → Hosting → Release history → 'Rollback' on a previous deploy.
# 11) Atomic deploys + caching
# Each deploy is immutable; CDN versions assets together.
# Use long-lived Cache-Control on hashed assets, no-cache on HTML, so HTML always re-validates while bundles stay cached.
# 12) Headers for security
# Add CSP, HSTS, X-Content-Type-Options.
# Firebase Hosting headers honoured at the edge — no need to ship them from your origin app.
# 13) Local emulator
firebase emulators:start --only hosting
# Serves at http://localhost:5000 — full firebase.json rewrites tested locally.
# 14) When NOT to use Firebase Hosting
# • Backend-driven sites with no static layer — Cloud Run alone may be simpler
# • Need Edge functions / server-rendered React at the edge — try Cloudflare Pages / Vercel
# • Strict compliance regions outside Google's footprint — pick a regional CDN
# 15) Monitoring
# Firebase Console → Hosting → bandwidth, request count, error rate per release.
# For deeper metrics, attach Google Analytics 4 + Cloud Logging.
# 16) Common bugs
# • Forgot to rebuild before deploy → old assets shipped
# • SPA fallback overrides API routes → list API rewrites BEFORE the catch-all
# • Long-cached HTML with new bundle hashes → users see stale shell
# • Browser cached service worker not updating → registered 'skipWaiting' or new SW version
# • Custom domain DNS misconfigured → 'Setup pending'; check TXT + A records
# • CSP header blocking inline scripts → use nonces + add to header config
# • Preview channel expired → users see 404; recreate or push to live
# • Cross-project deploys requiring different service accounts → secrets per env
# • Multi-site target not applied → deploy ambiguous; firebase target:apply
Why it matters
Firebase Hosting is a polished static + CDN with rewrites to Cloud Functions or Cloud Run for dynamic routes. Configure cache headers (immutable on hashed assets, no-cache on HTML), use preview channels for PRs, set up GitHub Actions for one-click deploys, and add security headers right in firebase.json.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
# firebase.json
{
"hosting": {
"public": "dist",
"rewrites": [{ "source": "**", "destination": "/index.html" }]
}
}
firebase deploy --only hosting
Try it Yourself »
Exercise
Deploy only hosting.
firebase deploy --only
Seven letters.
Discussion
Loading…