Firebase Hosting

WebホスティングSPAPWAFirebaseGoogleサーバーレスCDN

Webホスティングプラットフォーム

Firebase Hosting

概要

Firebase HostingはGoogleが提供する高速で安全なWebホスティングサービスです。SPAやPWA向けに最適化され、Firebase他サービスとの統合によりフルスタック開発をサポートします。モバイルアプリ開発と連携したWebアプリケーションで人気で、Firebase Authentication、Firestore等との統合により、迅速なプロトタイプ開発で重宝されています。

詳細

2014年にGoogleに買収されたFirebaseの一部として提供されるFirebase Hostingは、モダンなWebアプリケーション開発に特化したホスティングサービスです。GoogleのグローバルCDNネットワークを活用し、世界中の180以上の場所から高速配信を実現。特にSPA(Single Page Application)やPWA(Progressive Web App)に最適化されており、自動SSL証明書、カスタムドメイン、ロールバック機能を標準提供。Firebase Authentication、Cloud Firestore、Cloud Functionsなどとの緊密な統合により、フロントエンドからバックエンドまでの完全なソリューションを提供します。

メリット・デメリット

メリット

  • 高速グローバルCDN: Googleの180+エッジロケーションからの配信
  • Firebase生態系との統合: Authentication、Firestore、Functions等との完全統合
  • SPA/PWA最適化: モダンなWebアプリケーションに特化した機能
  • 自動SSL証明書: Let's Encryptによる無料SSL
  • 簡単なロールバック: ワンクリックでの前バージョン復元
  • リアルタイム機能: Firebase Realtimeと組み合わせたライブ更新
  • 無料利用枠: 月10GBまでの無料ストレージと転送量

デメリット

  • Google依存: Googleサービスへの高い依存度
  • 制限事項: 大容量ファイルや高トラフィックでの課金増加
  • 学習コストの高さ: Firebase生態系全体の理解が必要
  • サーバーサイド制限: 静的サイトとCloud Functions以外の制約

参考ページ

書き方の例

基本的なセットアップとプロジェクト設定

# Firebase CLIのインストール
npm install -g firebase-tools

# Firebaseにログイン
firebase login

# プロジェクトの初期化
firebase init hosting

# ローカル開発サーバー
firebase serve --only hosting --port 5000

# エミュレーター(全機能)の起動
firebase emulators:start
// firebase.json - Firebase設定
{
  "hosting": {
    "public": "dist",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
      {
        "source": "/api/**",
        "function": "api"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ],
    "headers": [
      {
        "source": "/service-worker.js",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "no-cache"
          }
        ]
      },
      {
        "source": "**/*.@(jpg|jpeg|gif|png|svg|webp)",
        "headers": [
          {
            "key": "Cache-Control",
            "value": "max-age=31536000"
          }
        ]
      }
    ],
    "cleanUrls": true,
    "trailingSlash": false
  },
  "functions": {
    "source": "functions",
    "node": 18
  },
  "emulators": {
    "hosting": {
      "port": 5000
    },
    "functions": {
      "port": 5001
    },
    "firestore": {
      "port": 8080
    },
    "ui": {
      "enabled": true,
      "port": 4000
    }
  }
}

静的サイトデプロイ

# 本番デプロイ
firebase deploy --only hosting

# プレビューチャンネルでのデプロイ
firebase hosting:channel:deploy preview-feature

# 特定のプロジェクトへのデプロイ
firebase deploy --project my-project-id

# 複数サイトの管理
firebase target:apply hosting main my-main-site
firebase target:apply hosting admin my-admin-site
firebase deploy --only hosting:main
# .github/workflows/firebase.yml - GitHub Actions
name: Deploy to Firebase Hosting

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
        
      - name: Deploy to Firebase Hosting
        uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}'
          projectId: my-project-id
          channelId: live

フレームワーク統合(Next.js、React、Vue)

// next.config.js - Next.js + Firebase
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export',
  trailingSlash: true,
  images: {
    unoptimized: true
  },
  
  // Firebase Hosting用の設定
  assetPrefix: process.env.NODE_ENV === 'production' ? undefined : '',
  
  // 環境変数の設定
  env: {
    FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID,
    FIREBASE_API_KEY: process.env.FIREBASE_API_KEY,
  },
};

module.exports = nextConfig;
<!-- Vue.js + Firebase統合 -->
<template>
  <div id="app">
    <nav>
      <router-link to="/">Home</router-link>
      <router-link to="/about">About</router-link>
      <button v-if="user" @click="logout">Logout</button>
      <button v-else @click="login">Login</button>
    </nav>
    
    <main>
      <router-view />
    </main>
  </div>
</template>

<script>
import { auth } from './firebase/config';
import { signInWithPopup, GoogleAuthProvider, signOut } from 'firebase/auth';

export default {
  name: 'App',
  data() {
    return {
      user: null
    };
  },
  
  created() {
    auth.onAuthStateChanged(user => {
      this.user = user;
    });
  },
  
  methods: {
    async login() {
      const provider = new GoogleAuthProvider();
      try {
        await signInWithPopup(auth, provider);
      } catch (error) {
        console.error('Login failed:', error);
      }
    },
    
    async logout() {
      try {
        await signOut(auth);
      } catch (error) {
        console.error('Logout failed:', error);
      }
    }
  }
};
</script>
// firebase/config.ts - Firebase初期化
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore } from 'firebase/firestore';
import { getFunctions } from 'firebase/functions';
import { getStorage } from 'firebase/storage';

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID,
  appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID
};

// Firebase初期化
const app = initializeApp(firebaseConfig);

// サービスの初期化
export const auth = getAuth(app);
export const db = getFirestore(app);
export const functions = getFunctions(app);
export const storage = getStorage(app);

export default app;

カスタムドメインとSSL

# カスタムドメインの追加
firebase hosting:sites:create my-custom-site
firebase target:apply hosting main my-custom-site

# ドメインの設定
firebase hosting:sites:list
firebase hosting:sites:get my-custom-site

# SSL証明書は自動で設定される(Let's Encrypt)
// firebase.json - マルチサイト設定
{
  "hosting": [
    {
      "target": "main",
      "public": "dist",
      "rewrites": [
        {
          "source": "**",
          "destination": "/index.html"
        }
      ]
    },
    {
      "target": "admin",
      "public": "admin-dist",
      "rewrites": [
        {
          "source": "/admin/**",
          "destination": "/admin/index.html"
        }
      ]
    }
  ]
}
// .firebaserc - プロジェクト設定
{
  "projects": {
    "default": "my-project-id",
    "staging": "my-project-staging",
    "production": "my-project-prod"
  },
  "targets": {
    "my-project-id": {
      "hosting": {
        "main": ["my-main-site"],
        "admin": ["my-admin-site"]
      }
    }
  }
}

サーバーレスファンクションとAPI

// functions/index.js - Cloud Functions
const { onRequest } = require('firebase-functions/v2/https');
const { onDocumentCreated } = require('firebase-functions/v2/firestore');

// HTTP APIエンドポイント
exports.api = onRequest({
  cors: true,
  region: 'asia-northeast1'
}, async (req, res) => {
  const { method, path } = req;
  
  if (method === 'GET' && path === '/users') {
    // Firestoreからユーザー一覧を取得
    const admin = require('firebase-admin');
    const db = admin.firestore();
    
    try {
      const snapshot = await db.collection('users').get();
      const users = [];
      snapshot.forEach(doc => {
        users.push({ id: doc.id, ...doc.data() });
      });
      
      res.json({ users });
    } catch (error) {
      res.status(500).json({ error: error.message });
    }
  } else {
    res.status(404).json({ error: 'Not found' });
  }
});

// Firestoreトリガー
exports.onUserCreate = onDocumentCreated('users/{userId}', (event) => {
  const snapshot = event.data;
  const data = snapshot.data();
  
  console.log(`New user created: ${data.name}`);
  
  // ウェルカムメール送信等の処理
  return null;
});
// src/services/api.ts - フロントエンド API クライアント
import { getFunctions, httpsCallable } from 'firebase/functions';
import { functions } from '../firebase/config';

class ApiService {
  private functions = getFunctions();

  // HTTP Callable Function
  async getUsers() {
    try {
      const getUsersFunction = httpsCallable(this.functions, 'getUsers');
      const result = await getUsersFunction();
      return result.data;
    } catch (error) {
      console.error('Error fetching users:', error);
      throw error;
    }
  }

  // REST API呼び出し
  async fetchUserData(userId: string) {
    try {
      const response = await fetch(`/api/users/${userId}`);
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}`);
      }
      return await response.json();
    } catch (error) {
      console.error('Error fetching user data:', error);
      throw error;
    }
  }

  // リアルタイムデータリスニング
  subscribeToUserUpdates(userId: string, callback: (data: any) => void) {
    const { doc, onSnapshot } = require('firebase/firestore');
    const { db } = require('../firebase/config');
    
    const userDoc = doc(db, 'users', userId);
    return onSnapshot(userDoc, callback);
  }
}

export const apiService = new ApiService();

CI/CDと本番最適化

# .github/workflows/firebase-preview.yml - プレビューチャンネル
name: Firebase Preview Deploy

on:
  pull_request:
    branches: [ main ]

jobs:
  build_and_preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
        
      - name: Deploy to Preview Channel
        uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.FIREBASE_SERVICE_ACCOUNT }}'
          projectId: my-project-id
          expires: 7d
        id: firebase_preview
        
      - name: Comment Preview URL
        uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: '🔥 Preview URL: ${{ steps.firebase_preview.outputs.details_url }}'
            })
# Firebase CLI コマンド集
firebase projects:list                    # プロジェクト一覧
firebase use --add                        # プロジェクト追加
firebase hosting:sites:list               # サイト一覧
firebase hosting:channel:list             # チャンネル一覧

# ログとモニタリング
firebase functions:log                     # Functions ログ
firebase hosting:clone source-site-id destination-site-id  # サイト複製

# セキュリティルール
firebase deploy --only firestore:rules    # Firestore ルール
firebase deploy --only storage:rules      # Storage ルール

# データベース操作
firebase firestore:delete --all-collections  # 全データ削除(注意)
firebase firestore:indexes                # インデックス管理

# パフォーマンス最適化
firebase hosting:disable                  # ホスティング無効化
firebase experiments:enable webframeworks # 実験的機能
// firebase.json - 高度な設定
{
  "hosting": {
    "public": "dist",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    
    // PWA設定
    "rewrites": [
      {
        "source": "/sw.js",
        "destination": "/sw.js"
      },
      {
        "source": "**",
        "destination": "/index.html"
      }
    ],
    
    // セキュリティヘッダー
    "headers": [
      {
        "source": "**",
        "headers": [
          {
            "key": "Strict-Transport-Security",
            "value": "max-age=31536000; includeSubDomains"
          },
          {
            "key": "X-Content-Type-Options",
            "value": "nosniff"
          }
        ]
      }
    ],
    
    // リダイレクト設定
    "redirects": [
      {
        "source": "/old-page",
        "destination": "/new-page",
        "type": 301
      }
    ],
    
    // A/Bテスト設定
    "appAssociation": "AUTO",
    "cleanUrls": true,
    "trailingSlash": false
  },
  
  // Remote Config
  "remoteconfig": {
    "template": "remoteconfig.template.json"
  },
  
  // App Distribution
  "appDistribution": {
    "serviceAccountFile": "path/to/service-account.json",
    "releaseNotesFile": "RELEASE_NOTES.md"
  }
}