Multi-Site Hosting with Firebase
Manage multiple websites within a single Firebase project. Perfect for marketing sites, documentation, blogs, and microsites all under one project umbrella.
When to Use Multi-Site Hosting
Good Use Cases ✅
- Marketing site + App -
example.com
+app.example.com
- Documentation sites -
docs.example.com
- Blog platforms -
blog.example.com
- Regional sites -
uk.example.com
,de.example.com
- Staging environments -
staging.example.com
- Microsites - Campaign or event-specific sites
When to Use Separate Projects ❌
- Different billing requirements
- Separate team permissions needed
- Completely unrelated sites
- Different security requirements
Setting Up Multiple Sites
Step 1: Create Additional Sites
# Create new sites (max 36 sites per project)
firebase hosting:sites:create app-site
firebase hosting:sites:create blog-site
firebase hosting:sites:create docs-site
# List all sites
firebase hosting:sites:list
Site IDs must be globally unique and will get URLs like:
app-site.web.app
blog-site.web.app
docs-site.web.app
Step 2: Configure Targets
# Apply targets to map sites to deploy targets
firebase target:apply hosting app app-site
firebase target:apply hosting blog blog-site
firebase target:apply hosting docs docs-site
# View current targets
firebase target:list
Step 3: Update firebase.json
{
"hosting": [
{
"target": "app",
"public": "dist/app",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"rewrites": [{
"source": "**",
"destination": "/index.html"
}],
"headers": [{
"source": "**/*.@(js|css)",
"headers": [{
"key": "Cache-Control",
"value": "max-age=31536000"
}]
}]
},
{
"target": "blog",
"public": "dist/blog",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"cleanUrls": true,
"trailingSlash": false,
"headers": [{
"source": "**/*.@(jpg|jpeg|gif|png)",
"headers": [{
"key": "Cache-Control",
"value": "max-age=86400"
}]
}]
},
{
"target": "docs",
"public": "dist/docs",
"ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
"redirects": [{
"source": "/v1/**",
"destination": "https://legacy-docs.example.com/v1/:splat",
"type": 301
}]
}
]
}
Project Structure
Monorepo Structure
my-project/
├── apps/
│ ├── main/
│ │ ├── src/
│ │ ├── public/
│ │ └── package.json
│ ├── blog/
│ │ ├── content/
│ │ ├── themes/
│ │ └── package.json
│ └── docs/
│ ├── docs/
│ ├── static/
│ └── package.json
├── shared/
│ └── components/
├── firebase.json
├── .firebaserc
└── package.json
Build Configuration
Root package.json
:
{
"scripts": {
"build:app": "cd apps/main && npm run build",
"build:blog": "cd apps/blog && npm run build",
"build:docs": "cd apps/docs && npm run build",
"build:all": "npm run build:app && npm run build:blog && npm run build:docs",
"deploy:app": "npm run build:app && firebase deploy --only hosting:app",
"deploy:blog": "npm run build:blog && firebase deploy --only hosting:blog",
"deploy:docs": "npm run build:docs && firebase deploy --only hosting:docs",
"deploy:all": "npm run build:all && firebase deploy --only hosting"
}
}
Deployment Strategies
Deploy Individual Sites
# Deploy single site
firebase deploy --only hosting:app
firebase deploy --only hosting:blog
# Deploy multiple sites
firebase deploy --only hosting:app,hosting:blog
# Deploy all sites
firebase deploy --only hosting
CI/CD for Multi-Site
# GitHub Actions example
name: Deploy Multi-Site
on:
push:
branches: [main]
paths:
- 'apps/**'
- 'firebase.json'
jobs:
deploy-app:
if: contains(github.event.head_commit.modified, 'apps/main/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci && npm run build:app
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
target: app
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}'
deploy-blog:
if: contains(github.event.head_commit.modified, 'apps/blog/')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: npm ci && npm run build:blog
- uses: FirebaseExtended/action-hosting-deploy@v0
with:
target: blog
firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}'
Custom Domains
Setting Up Domains
# Each site can have custom domains
# Configure in Firebase Console or via API
# Example domain mapping:
# app-site → app.example.com
# blog-site → blog.example.com
# docs-site → docs.example.com
# main-site → example.com, www.example.com
DNS Configuration
For each subdomain:
Type: A
Name: app
Value: Firebase IP addresses
Type: AAAA
Name: app
Value: Firebase IPv6 addresses
Type: TXT (for verification)
Name: app
Value: firebase=YOUR_VERIFICATION_CODE
Domain Management Tips
- Verify root domain first - Makes subdomain verification easier
- Use CNAME for subdomains - When possible, simpler than A records
- Plan SSL provisioning time - Can take up to 24 hours
- Set up redirects - www to non-www or vice versa
Preview Channels for Multi-Site
Site-Specific Previews
# Create preview channel for specific site
firebase hosting:channel:deploy preview --only hosting:app
firebase hosting:channel:deploy feature-x --only hosting:blog
# URLs will be:
# app-site--preview-HASH.web.app
# blog-site--feature-x-HASH.web.app
Cross-Site Testing
Test interactions between sites:
// In app site, reference blog preview
const blogUrl = process.env.REACT_APP_BLOG_URL || 'https://blog.example.com';
// For preview testing
// REACT_APP_BLOG_URL=https://blog-site--preview-x8yn.web.app
Shared Resources
Sharing Assets
{
"hosting": [{
"target": "app",
"public": "dist/app",
"rewrites": [{
"source": "/shared/**",
"destination": "https://assets-site.web.app/shared/:splat"
}]
}]
}
Shared Authentication
// Configure Firebase Auth to work across subdomains
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL);
// Set auth domain in Firebase Console
// authDomain: "example.com" (not app.example.com)
Cross-Site Communication
// PostMessage for cross-origin communication
// From app.example.com to blog.example.com
window.postMessage({
type: 'user-login',
userId: user.uid
}, 'https://blog.example.com');
// Listen on blog.example.com
window.addEventListener('message', (event) => {
if (event.origin !== 'https://app.example.com') return;
if (event.data.type === 'user-login') {
// Handle login state
}
});
Performance Optimization
Site-Specific Optimization
{
"hosting": [
{
"target": "app",
"headers": [{
"source": "**/*.js",
"headers": [{
"key": "Cache-Control",
"value": "max-age=31536000, immutable"
}]
}]
},
{
"target": "blog",
"headers": [{
"source": "**/*.html",
"headers": [{
"key": "Cache-Control",
"value": "max-age=3600, s-maxage=86400"
}]
}]
}
]
}
Resource Prioritization
<!-- Preconnect to other sites -->
<link rel="preconnect" href="https://app.example.com">
<link rel="preconnect" href="https://blog.example.com">
<!-- DNS prefetch for external resources -->
<link rel="dns-prefetch" href="https://cdn.example.com">
Monitoring Multi-Site
Unified Analytics
// Track cross-site user journeys
gtag('config', 'GA_MEASUREMENT_ID', {
cookie_domain: 'example.com',
cookie_flags: 'SameSite=None;Secure',
linker: {
domains: ['example.com', 'app.example.com', 'blog.example.com']
}
});
Site-Specific Monitoring
# Monitor individual site metrics
# In Firebase Console → Hosting → Select site
# CLI commands for specific sites
firebase hosting:sites:get app-site
firebase hosting:versions:list --site blog-site
Best Practices
1. Naming Conventions
# Use clear, consistent naming
main-site # Main marketing site
app-site # Application
blog-site # Blog
docs-site # Documentation
staging-site # Staging environment
2. Build Optimization
// Share dependencies with workspaces
// package.json
{
"workspaces": [
"apps/*",
"shared/*"
]
}
3. Deployment Strategy
- Deploy incrementally, not all sites at once
- Use preview channels for testing
- Implement proper rollback procedures
- Monitor each site independently
4. Cost Management
- Monitor bandwidth per site
- Optimize assets for each site's needs
- Consider CDN caching strategies
- Review usage regularly
Troubleshooting
Common Issues
Issue | Solution |
---|---|
"Site ID already exists" | Choose globally unique ID |
Deploy affects wrong site | Check target configuration |
Domain verification fails | Verify DNS propagation |
Cross-site auth issues | Configure auth domain correctly |
Preview channels mixed up | Use --only hosting:TARGET |
Debug Commands
# Check site configuration
firebase hosting:sites:get SITE_ID
# Verify targets
firebase target:list
# Test specific site locally
firebase emulators:start --only hosting
# Visit localhost:5000 and localhost:5001 for different sites
Migration Guide
Moving from Single to Multi-Site
-
Backup current configuration
cp firebase.json firebase.json.backup
-
Create new site
firebase hosting:sites:create new-main-site
-
Update configuration
- Convert single hosting to array
- Add target configuration
- Update build scripts
-
Test thoroughly
- Deploy to preview channels first
- Verify all redirects work
- Check custom domains
-
Switch DNS
- Update DNS records to new site
- Monitor for issues
Next Steps
- Configure Custom Domains for each site
- Set up Preview Channels for testing
- Implement Monitoring for all sites
- Review Performance optimization strategies
Pro Tip: Start with 2-3 sites and expand as needed. Multi-site hosting is powerful but requires good organization. Use consistent naming and clear documentation! 🚀