To set caching rules in Firebase Hosting, add a headers block in your firebase.json configuration that matches file patterns with Cache-Control directives. Use long max-age values with immutable for fingerprinted assets like JavaScript bundles, short max-age for HTML files that change frequently, and no-cache for dynamic content. Firebase Hosting automatically applies CDN caching, so your headers control both browser and edge cache behavior.
Configuring Cache-Control Headers in Firebase Hosting
Firebase Hosting serves all files through a global CDN, and your caching rules determine how long browsers and CDN edge nodes keep copies of your files before re-fetching. Proper caching dramatically improves load times for returning visitors while ensuring they always get fresh HTML content. This tutorial walks through setting up a complete caching strategy in firebase.json with real-world patterns for SPAs, static sites, and asset pipelines.
Prerequisites
- A Firebase project with Hosting initialized (firebase init hosting)
- Firebase CLI installed and authenticated
- A built frontend project with fingerprinted assets (e.g., Vite, Next.js, or CRA build output)
- Basic understanding of HTTP caching and Cache-Control headers
Step-by-step guide
Add a headers section to firebase.json
Add a headers section to firebase.json
Open your firebase.json file and add a headers array inside the hosting configuration. Each entry in the array specifies a source glob pattern and an array of key-value header pairs. The source pattern uses glob syntax where ** matches any path segment and * matches any filename. Firebase evaluates headers top to bottom and applies all matching rules, so more specific patterns should come after general ones.
1{2 "hosting": {3 "public": "dist",4 "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],5 "headers": [6 {7 "source": "**/*.html",8 "headers": [9 {10 "key": "Cache-Control",11 "value": "no-cache"12 }13 ]14 }15 ]16 }17}Expected result: Your firebase.json now has a headers section that sets Cache-Control for HTML files.
Set long-lived caching for fingerprinted static assets
Set long-lived caching for fingerprinted static assets
Build tools like Vite and webpack produce files with content hashes in the filename (e.g., main.a1b2c3d4.js). Since the filename changes whenever the content changes, you can safely cache these files for a very long time. Set max-age to one year (31536000 seconds) with the immutable directive, which tells the browser to never revalidate the file during its max-age period. Apply this to JavaScript, CSS, images, and fonts in your build output.
1{2 "hosting": {3 "public": "dist",4 "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],5 "headers": [6 {7 "source": "**/*.html",8 "headers": [9 {10 "key": "Cache-Control",11 "value": "no-cache"12 }13 ]14 },15 {16 "source": "**/*.@(js|css)",17 "headers": [18 {19 "key": "Cache-Control",20 "value": "public, max-age=31536000, immutable"21 }22 ]23 },24 {25 "source": "**/*.@(jpg|jpeg|png|gif|svg|webp|avif|ico|woff2|woff|ttf)",26 "headers": [27 {28 "key": "Cache-Control",29 "value": "public, max-age=31536000, immutable"30 }31 ]32 }33 ]34 }35}Expected result: JavaScript, CSS, images, and font files are cached for one year with the immutable directive.
Configure caching for your SPA index.html
Configure caching for your SPA index.html
Single-page applications serve the same index.html for all routes using a rewrite rule. This file must never be aggressively cached because it contains the script tags pointing to your fingerprinted bundles. When you deploy a new version, users need to fetch the updated index.html to get the new bundle references. Use no-cache to force revalidation on every visit, or set a short max-age like 300 seconds (5 minutes) if you want some caching benefit.
1{2 "hosting": {3 "public": "dist",4 "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],5 "rewrites": [6 {7 "source": "**",8 "destination": "/index.html"9 }10 ],11 "headers": [12 {13 "source": "/index.html",14 "headers": [15 {16 "key": "Cache-Control",17 "value": "no-cache"18 }19 ]20 },21 {22 "source": "/",23 "headers": [24 {25 "key": "Cache-Control",26 "value": "no-cache"27 }28 ]29 }30 ]31 }32}Expected result: The index.html file is never served from a stale cache, while all fingerprinted assets load instantly from the browser cache.
Add security headers alongside caching headers
Add security headers alongside caching headers
The headers configuration in firebase.json supports any HTTP response header, not just Cache-Control. You can add security headers like X-Content-Type-Options, X-Frame-Options, and Referrer-Policy alongside your caching rules. Apply these globally using a ** source pattern to cover all responses.
1{2 "source": "**",3 "headers": [4 {5 "key": "X-Content-Type-Options",6 "value": "nosniff"7 },8 {9 "key": "X-Frame-Options",10 "value": "DENY"11 },12 {13 "key": "Referrer-Policy",14 "value": "strict-origin-when-cross-origin"15 }16 ]17}Expected result: All responses include security headers in addition to their specific caching rules.
Deploy and verify your caching headers
Deploy and verify your caching headers
Deploy the updated firebase.json to Firebase Hosting and verify the headers are applied correctly. Use your browser's DevTools Network tab to inspect response headers on different file types. Check that HTML files show no-cache, fingerprinted JS/CSS files show the long max-age with immutable, and all files include your security headers. You can also use a preview channel to test before deploying to production.
1# Deploy to a preview channel first2firebase hosting:channel:deploy staging34# Once verified, deploy to production5firebase deploy --only hostingExpected result: DevTools Network tab shows the correct Cache-Control headers for each file type. HTML returns no-cache and assets return max-age=31536000, immutable.
Complete working example
1{2 "hosting": {3 "public": "dist",4 "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],5 "rewrites": [6 {7 "source": "**",8 "destination": "/index.html"9 }10 ],11 "headers": [12 {13 "source": "**",14 "headers": [15 {16 "key": "X-Content-Type-Options",17 "value": "nosniff"18 },19 {20 "key": "X-Frame-Options",21 "value": "DENY"22 },23 {24 "key": "Referrer-Policy",25 "value": "strict-origin-when-cross-origin"26 }27 ]28 },29 {30 "source": "/index.html",31 "headers": [32 {33 "key": "Cache-Control",34 "value": "no-cache"35 }36 ]37 },38 {39 "source": "/",40 "headers": [41 {42 "key": "Cache-Control",43 "value": "no-cache"44 }45 ]46 },47 {48 "source": "**/*.html",49 "headers": [50 {51 "key": "Cache-Control",52 "value": "no-cache"53 }54 ]55 },56 {57 "source": "**/*.@(js|css)",58 "headers": [59 {60 "key": "Cache-Control",61 "value": "public, max-age=31536000, immutable"62 }63 ]64 },65 {66 "source": "**/*.@(jpg|jpeg|png|gif|svg|webp|avif|ico)",67 "headers": [68 {69 "key": "Cache-Control",70 "value": "public, max-age=31536000, immutable"71 }72 ]73 },74 {75 "source": "**/*.@(woff2|woff|ttf|eot)",76 "headers": [77 {78 "key": "Cache-Control",79 "value": "public, max-age=31536000, immutable"80 }81 ]82 },83 {84 "source": "/manifest.json",85 "headers": [86 {87 "key": "Cache-Control",88 "value": "no-cache"89 }90 ]91 }92 ]93 }94}Common mistakes when setting Caching Rules in Firebase Hosting
Why it's a problem: Setting a long max-age on index.html, causing users to see stale app versions after deployment
How to avoid: Always use no-cache for HTML files. Only apply long max-age to fingerprinted assets where the filename changes when content changes.
Why it's a problem: Using no-store instead of no-cache for HTML files, preventing any caching benefit including 304 responses
How to avoid: Use no-cache to allow conditional caching with revalidation. Reserve no-store for truly sensitive data like authenticated API responses.
Why it's a problem: Forgetting to set caching headers on the root path / separately from /index.html in SPA configurations
How to avoid: Add Cache-Control headers for both / and /index.html since Firebase Hosting treats these as separate routes even with SPA rewrites.
Why it's a problem: Deploying without fingerprinted filenames but setting long max-age, causing browsers to serve stale JS/CSS
How to avoid: Ensure your build tool generates content-hashed filenames (e.g., main.a1b2c3.js) before using immutable caching. If filenames do not change, use shorter max-age values.
Best practices
- Use no-cache for HTML files and max-age=31536000, immutable for fingerprinted static assets
- Always pair long max-age with content-hashed filenames from your build tool to avoid serving stale files
- Add security headers (X-Content-Type-Options, X-Frame-Options) in the same headers configuration
- Test caching behavior using preview channels before deploying to production
- Set no-cache on manifest.json and service worker files to ensure updates propagate immediately
- Use the ** glob pattern for global headers and more specific patterns for per-file-type caching
- Verify headers in DevTools Network tab after each deployment to catch configuration errors
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I need to set up caching rules in my firebase.json for a React SPA hosted on Firebase. Show me how to configure Cache-Control headers with long caching for fingerprinted JS/CSS assets and no-cache for HTML files, plus security headers.
Configure the headers section in firebase.json for a Vite + React SPA on Firebase Hosting. Set immutable caching for hashed JS, CSS, images, and fonts, no-cache for index.html and the root path, and add X-Content-Type-Options and X-Frame-Options security headers.
Frequently asked questions
Does Firebase Hosting have its own CDN caching separate from my Cache-Control headers?
Yes. Firebase Hosting uses a global CDN that caches responses at edge nodes. Your Cache-Control headers influence both the browser cache and the CDN cache. After a new deployment, Firebase automatically invalidates the CDN cache for all files.
What happens to cached files when I deploy a new version?
Firebase Hosting purges the CDN cache on every deployment, so edge nodes fetch fresh copies. However, browsers that already cached files with a long max-age will continue using those cached versions until the max-age expires, which is why fingerprinted filenames are essential.
Can I set different caching rules for different directories?
Yes. Use directory-specific glob patterns like assets/** or images/** in your source field to apply different Cache-Control values per directory.
What is the difference between no-cache and no-store?
no-cache allows the browser to cache the file but requires revalidation with the server on every request (enabling 304 Not Modified responses). no-store tells the browser to never cache the file at all, re-downloading it entirely on every request.
Should I set caching headers on my service worker file?
Yes. Service worker files (sw.js, service-worker.js) should use no-cache or max-age=0 to ensure the browser always checks for updates. Browsers already limit service worker caching to 24 hours, but explicit headers prevent edge cases.
How do I verify that my caching headers are working correctly?
Open your browser DevTools Network tab, load your site, and inspect the Response Headers for each file. Check that HTML files show no-cache and fingerprinted assets show your long max-age value. Also check the Size column for 'disk cache' to confirm caching is active.
Can RapidDev help optimize my Firebase Hosting configuration for performance?
Yes. RapidDev can audit and optimize your Firebase Hosting setup including caching strategies, CDN configuration, security headers, and SPA routing for maximum performance and security.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation