Firebase Hosting skill for Codex

Static sites, API rewrites and preview channels. Codex writes firebase.json so hashed assets cache for a year, HTML revalidates, and /api routes reach a 2nd gen function or Cloud Run service on the same domain. It knows the __session cookie rule and when the CDN caches function responses, sets up preview channels per pull request, always scopes deploys with --only, and checks the active project alias before anything goes live.

  • Cloud Platforms
  • $firebase-hosting
  • 177 lines
  • 2 files
  • ~1,750 tokens
  • MIT
  • Updated

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- firebase-hosting

Installs to ~/.agents/skills/firebase-hosting/ for every project on this machine.

104-line POSIX script. No sudo, no telemetry. Read install.sh

Use it in Codex

Start a prompt with $firebase-hosting, or pick it from /skills. You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.

What Codex reads before loading it

Configures and deploys Firebase Hosting - firebase.json public directory, rewrites to Cloud Functions (2nd gen) and Cloud Run, SPA fallbacks, redirects, headers and cache control, cleanUrls, multiple sites and deploy targets, preview channels for pull requests, the local emulator, GitHub Actions deploys, rollback and the difference from Firebase App Hosting. Use when deploying a static or SPA site to Firebase, wiring an API behind the same domain, rewrites return 404, function responses are not cached, or setting up per-PR preview URLs.

Use it when

  • Deploying a static site or SPA to Firebase Hosting
  • Routing API paths to Cloud Functions or Cloud Run behind Hosting
  • Function responses through Hosting are slow or never cached
  • Setting up preview channels or multiple Hosting sites

Use something else when

How to check the result

The emulator and a preview channel serve correct routes and rewrites, curl -sI shows the intended Cache-Control, and firebase use names the right project before deploy.

The mistake it prevents

Running firebase deploy without --only, which also redeploys functions, security rules and indexes along with the site.

~/.agents/skills/firebase-hosting/SKILL.md

--- name: firebase-hosting description: Configures and deploys Firebase Hosting - firebase.json public directory, rewrites to Cloud Functions (2nd gen) and Cloud Run, SPA fallbacks,… license: MIT ---

A clean Firebase Hosting setup serves hashed assets with long cache lifetimes and HTML with short ones, routes /api/** to a function or Cloud Run service in the same project, gives every pull request its own expiring preview channel, and deploys --only hosting so a site change never redeploys unrelated functions or rules.

Inspect first

  1. Read AGENTS.md, firebase.json, .firebaserc (project aliases and targets), the framework build output directory, and functions/ or Cloud Run source if present.
  2. Run firebase use to see the active project alias. Deploying to the wrong alias is the most common incident. firebase commands that touch projects need network and login (firebase login, or a service account in CI).
  3. Decide the product:
AppProduct
Static site, SPA (React, Vue, Angular CSR), docsFirebase Hosting
Static site plus a small APIHosting with rewrites to Cloud Functions or Cloud Run
Next.js or Angular with SSR as the main appFirebase App Hosting (apphosting.yaml, Git-based rollouts), not classic Hosting
Container you already run on Cloud RunHosting rewrite to Cloud Run for the custom domain and CDN

firebase.json baseline

{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "cleanUrls": true,
    "trailingSlash": false,
    "redirects": [
      { "source": "/blog/:slug", "destination": "/articles/:slug", "type": 301 }
    ],
    "rewrites": [
      { "source": "/api/**", "function": { "functionId": "api", "region": "us-central1" } },
      { "source": "/render/**", "run": { "serviceId": "renderer", "region": "europe-west1" } },
      { "source": "**", "destination": "/index.html" }
    ],
    "headers": [
      {
        "source": "**/*.@(js|css|woff2|png|jpg|webp|avif|svg)",
        "headers": [{ "key": "Cache-Control", "value": "public, max-age=31536000, immutable" }]
      },
      {
        "source": "**/*.html",
        "headers": [{ "key": "Cache-Control", "value": "no-cache" }]
      }
    ]
  }
}

Rules:

  • Hosting serves an existing static file before evaluating rewrites, so the ** SPA fallback is safe. Redirects are evaluated before files.
  • Rewrites match in order; put specific API rules before the catch-all.
  • Long cache headers only on hashed filenames. Unhashed assets with a one-year immutable header cannot be updated in browsers.
  • The region in a function rewrite must match where the function is deployed.

API behind Hosting

// functions/src/index.ts
import { onRequest } from "firebase-functions/v2/https";
import express from "express";

const app = express();
app.get("/api/products", async (_req, res) => {
  res.set("Cache-Control", "public, max-age=60, s-maxage=300");
  res.json(await listProducts());
});

export const api = onRequest({ region: "us-central1", memory: "512MiB", concurrency: 80 }, app);
  • The Express route must include the /api prefix: Hosting forwards the full path.
  • Hosting’s CDN caches function and Cloud Run responses only when they send Cache-Control with public and s-maxage (or max-age). Without it every request invokes the function.
  • Cookies are stripped from requests forwarded to functions and Cloud Run, except one named __session. Use that name for the session cookie.
  • Requests proxied through Hosting have a fixed timeout around one minute even if the function allows longer; long work must be asynchronous.

Multiple sites and targets

firebase target:apply hosting marketing acme-marketing
firebase target:apply hosting app acme-app
{
  "hosting": [
    { "target": "marketing", "public": "apps/marketing/dist" },
    { "target": "app", "public": "apps/web/dist", "rewrites": [{ "source": "**", "destination": "/index.html" }] }
  ]
}
firebase deploy --only hosting:app

Local loop and deploys

firebase emulators:start --only hosting,functions     # serves on localhost; works offline after install
firebase hosting:channel:deploy pr-123 --expires 7d    # preview URL, does not touch live
firebase hosting:channel:list
firebase deploy --only hosting -m "release 2026-10-28" # live: confirm with the user first
firebase hosting:clone acme-app:pr-123 acme-app:live   # promote a tested preview to live (confirm first)

Preview channels share the project’s backend (Firestore, Functions). A preview is not an isolated staging environment; use a separate Firebase project for that and switch with firebase use staging.

Rollback: in the console’s Hosting release history choose “Rollback”, or clone a known good channel or version to live. Only static content and config roll back; functions and databases do not.

GitHub Actions

firebase init hosting:github creates workflows that deploy a preview channel per PR and live on merge using FirebaseExtended/action-hosting-deploy. Review the generated service account: it gets deploy permissions to the project, so store its key as a repository secret, or prefer Workload Identity Federation with google-github-actions/auth to avoid long-lived keys.

- uses: FirebaseExtended/action-hosting-deploy@v0
  with:
    repoToken: ${{ secrets.GITHUB_TOKEN }}
    firebaseServiceAccount: ${{ secrets.FIREBASE_SERVICE_ACCOUNT }}
    projectId: acme-app
    expires: 7d

Debug table

SymptomCheck
Deep link returns 404Missing ** rewrite to /index.html, or wrong public directory
/api returns the SPA HTMLAPI rewrite after the catch-all, or function name or region mismatch
Function returns 404 for /api/xExpress routes defined without the /api prefix
Logged-in user loses session through HostingCookie not named __session
Function invoked on every request despite cachingNo public plus s-maxage in the response
Old site after deployLong max-age on HTML or unhashed assets; a stale service worker

Verification

  • firebase emulators:start serves the site; routes, rewrites and headers behave as expected.
  • A preview channel URL passes smoke tests; curl -sI shows intended Cache-Control.
  • firebase use shows the intended project before every deploy.
  • Live deploys and clone-to-live happen only after explicit user approval.

Gotchas

  • firebase deploy without --only deploys everything configured: functions, rules, indexes. Always scope it.
  • Hosting custom domains need DNS records verified; certificate provisioning can take a while after DNS changes.
  • The ignore list does not stop secrets in the build output from being published. Audit public for .env files and source maps you do not want public.
  • Each Hosting site keeps a capped number of releases; older ones are deleted automatically, so long-term rollback relies on rebuilding from git.

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

Gives the Codex app a display name, a one-line description and a starter prompt for this skill.

interface:
  display_name: "Firebase Hosting"
  short_description: "Static sites, API rewrites and preview channels"
  default_prompt: "$firebase-hosting Deploy our Vite React app to Firebase Hosting with an Express API behind /api"

About this skill

Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem