Ship Your Website for $0: Firebase Hosting Edition
I just moved my portfolio site from shared hosting to Firebase Hosting. The result: faster loads, free SSL, global CDN, and deploys that take one command instead of FTP uploads. Total monthly cost: $0.
Here's exactly how I did it, step by step, so you can do the same in under 30 minutes.
Why Firebase Hosting Over Shared Hosting
I was paying for shared hosting that gave me slow response times, manual SSL renewal headaches, and an FTP-based deploy workflow that felt like 2012. Firebase Hosting replaces all of that with a generous free tier:
- 10 GB storage — more than enough for any portfolio or blog
- 360 MB/day bandwidth (~10 GB/month) — plenty for a personal site
- Free SSL certificates that auto-renew (no more Let's Encrypt cron jobs)
- Global CDN — your site is served from the nearest edge location
- One-command deploys from your terminal
- Preview channels — test changes on a temporary URL before going live
- Custom domains with zero extra cost
The honest part: Firebase Hosting's free Spark plan doesn't include Cloud Functions. If you need server-side logic or your bandwidth exceeds 360 MB/day consistently, you'll need the Blaze (pay-as-you-go) plan. For a static portfolio site, though, you won't come close to those limits.
Prerequisites
Before we start, make sure you have:
- Node.js installed (v18 or later recommended)
- Firebase CLI installed globally:
npm install -g firebase-tools
- A Next.js project (or any static site — the Firebase parts apply to anything that outputs HTML/CSS/JS)
Step 1: Configure Next.js for Static Export
The key insight: Firebase Hosting serves static files. No server, no cost. We need to tell Next.js to output a fully static site.
Open your next.config.ts (or next.config.js) and add the export configuration:
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'export',
images: {
unoptimized: true,
},
};
export default nextConfig;
Two things are happening here:
output: 'export'tells Next.js to generate a static site in theout/directory instead of running a Node.js server. Every page becomes an HTML file.images: { unoptimized: true }disables the Next.js Image Optimization API, which requires a server. Your images still work — they just won't be dynamically resized at request time.
The tradeoff is worth it. No server means no hosting cost, and your images are served directly from a CDN. For a portfolio site, this is the right call.
Step 2: Create a Firebase Project and Init Hosting
First, log in to Firebase from your terminal:
firebase login
This opens a browser window for Google authentication. Once authenticated, create a new project:
firebase projects:create my-portfolio-site
Or create one through the Firebase Console if you prefer a UI.
Now initialize Firebase Hosting in your project directory:
firebase init hosting
The CLI walks you through a few questions. When it's done, you'll have a firebase.json file. Here's what mine looks like with the settings that matter:
{
"hosting": {
"public": "out",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [
{
"source": "**",
"destination": "/index.html"
}
],
"headers": [
{
"source": "**/*.@(jpg|jpeg|gif|png|svg|webp|ico)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
},
{
"source": "**/*.@(js|css)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
]
}
}
Let me break down the important parts:
"public": "out"— tells Firebase to serve files from theout/directory, which is where Next.js static export puts your built site."rewrites"— the SPA fallback. If someone hits a URL that doesn't match a file, serveindex.html. This keeps client-side routing working."headers"— aggressive caching for images, JS, and CSS.max-age=31536000is one year, andimmutabletells browsers not to even check for updates. Since Next.js hashes filenames on build, this is safe and makes repeat visits instant.
Step 3: Build and Deploy
This is the satisfying part. Two commands and your site is live:
npm run build && firebase deploy --only hosting
You'll see output like this:
=== Deploying to 'my-portfolio-site'...
i deploying hosting
i hosting[my-portfolio-site]: beginning deploy...
i hosting[my-portfolio-site]: found 47 files in out
✔ hosting[my-portfolio-site]: file upload complete
i hosting[my-portfolio-site]: finalizing version...
✔ hosting[my-portfolio-site]: version finalized
i hosting[my-portfolio-site]: releasing new version...
✔ hosting[my-portfolio-site]: release complete
✔ Deploy complete!
Project Console: https://console.firebase.google.com/project/my-portfolio-site/overview
Hosting URL: https://my-portfolio-site.web.app
That's it. Your site is live at my-portfolio-site.web.app with SSL, CDN, and proper caching headers. The whole deploy takes about 10 seconds.
Step 4: Connect Your Custom Domain
A .web.app URL is fine for testing, but you want your own domain. Head to the Firebase Console:
- Go to Hosting in the left sidebar
- Click Add custom domain
- Enter your domain (e.g.,
minhazpanara.com)
Firebase will ask you to verify ownership and point your DNS. You need two records at your domain registrar:
| Type | Name | Value |
|------|------|-------|
| A | @ | 199.36.158.100 |
| TXT | @ | hosting-site=my-portfolio-site |
If you want www to work too, add another custom domain for www.minhazpanara.com and Firebase will handle the redirect.
Important: Wait for SSL provisioning. It usually takes 10-30 minutes, but can take up to 24 hours if DNS propagation is slow.
The DNS Gotcha We Actually Hit
When I first set this up, SSL provisioning kept failing. The ACME challenge couldn't verify my domain. After some digging, I found the issue: my old hosting provider's A and AAAA records were still active.
Firebase needs to be the only thing responding on your domain's IP for the SSL certificate challenge to succeed. Delete your old A, AAAA, and any CNAME records pointing to your previous host before adding the Firebase records. Once I removed the stale records, SSL provisioned within 15 minutes.
If you're stuck on "Needs setup" for more than an hour, check for conflicting DNS records. That's almost always the problem.
Bonus: GitHub Actions Auto-Deploy
Manual deploys are fine, but automatic deploys on push are better. Here's a GitHub Actions workflow that deploys to Firebase whenever you push to main or development:
name: Deploy to Firebase Hosting
on:
push:
branches:
- main
- development
jobs:
build_and_deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
repoToken: '${{ secrets.GITHUB_TOKEN }}'
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}'
channelId: live
projectId: my-portfolio-site
To set this up:
- Generate a Firebase service account key:
firebase init hosting:github - The CLI automatically creates the GitHub secret
FIREBASE_SERVICE_ACCOUNTfor you - Push to main and watch the action run
Now every push triggers a build and deploy. No manual steps, no FTP, no SSH. Push code, site updates.
The Honest Cost Breakdown
Here's what Firebase Hosting actually costs at each tier:
| Resource | Free (Spark) | Paid (Blaze) | |----------|-------------|--------------| | Storage | 10 GB | $0.026/GB | | Bandwidth | 360 MB/day | $0.15/GB | | Custom domains | Unlimited | Unlimited | | SSL | Free, auto-renew | Same | | Sites per project | 36 | 36 | | Cloud Functions | Not available | Pay per invocation |
The verdict for portfolio sites: My entire site — HTML, CSS, JS, images, fonts — is about 5 MB of storage. Daily bandwidth is maybe 20-30 MB on a good day. I'm using less than 1% of the free tier limits. Unless your portfolio goes unexpectedly viral (a good problem to have), you're not paying a cent.
Even if you somehow hit the bandwidth limit, the Blaze plan has no minimum. You only pay for what you exceed. For most personal sites, that's still effectively $0.
What's Next
This is part one of a hosting comparison series. I'm running the same portfolio site on different platforms to compare the real-world experience — not just pricing pages, but actual setup friction, deploy speed, and gotchas.
Next week: the Vercel edition. Same site, different host. Vercel is purpose-built for Next.js, so you'd expect it to be easier. But is it? And how does the free tier actually compare? We'll find out.
If you're shipping a portfolio site right now and just want it live today, Firebase Hosting is a solid choice. Free, fast, and you can set it up during a lunch break. Go build something and put it on the internet.
Minhaz Panara
Full Stack Developer passionate about building modern web applications and sharing knowledge through blogging.