フロントエンド開発の先端を突っ走るNext.js
next.js アプリの初期化( npx create-next-app@latest <アプリ名> ) または yarn create next-app <アプリ名> または bun create next-app <アプリ名> )

Next.js AppRoute で Firesotreチャットアプリサンプル

● Firebase での設定

・Firebase Console から プロジェクトを作成
・Firestore Database → データベースを作成
・プロジェクトの設定 → ウェブアプリに Firebase を追加 → configを保存。

● Cloud Firestore のルールを変更する

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}

● Next.jsアプリを初期化

npx create-next-app@latest

● npm インストール

npm install firebase

● ファイルを設置

1. src/app/chat/[roomId]/page.tsx

import { ChatRoom } from "@/features/ChatRoom";

type PageProps = {
  params: {
    roomId: string;
  };
};
export default function ChatRoomPage({ params }: PageProps) {
  const roomId = params.roomId;

  if (!roomId) return <div>error</div>;

  return <ChatRoom roomId={roomId} />;
}

2. src/common/firebase/firebaseConfig.ts

import { initializeApp } from "firebase/app";
import { getFirestore } from "firebase/firestore";

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,
  measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID,
};

const app = initializeApp(firebaseConfig);

export const firestoreDb = getFirestore(app);

3. src/features/ChatRoom.tsx

"use client";

import { firestoreDb } from "@/common/firebase/firebaseConfig";
import {
  addDoc,
  collection,
  onSnapshot,
  query,
  Timestamp,
} from "firebase/firestore";
import { useRouter } from "next/navigation";
import { FC, useEffect, useState } from "react";

type Message = {
  id: string;
  text: string;
  createdAt: Timestamp;
  senderId: string;
  username: string;
};

interface ChatRoomProps {
  roomId: string;
}

export const ChatRoom: FC<ChatRoomProps> = ({ roomId }) => {
  const router = useRouter();
  const [messages, setMessages] = useState<Message[]>([]);
  const [newMessage, setNewMessage] = useState("");
  const [username, setUsername] = useState<string>("");
  const [currentUser, setCurrentUser] = useState<string | null>(null);

  useEffect(() => {
    if (!roomId) return;

    const q = query(collection(firestoreDb, `chatRooms/${roomId}/messages`));
    const unsubscribe = onSnapshot(q, (querySnapshot) => {
      const msgs: Message[] = [];
      querySnapshot.forEach((doc) => {
        const data = doc.data();
        msgs.push({
          id: doc.id,
          text: data.text,
          createdAt: data.createdAt,
          senderId: data.senderId,
          username: data.username,
        });
      });
      setMessages(msgs);
    });

    return () => unsubscribe();
  }, [roomId]);

  const handleSetUsername = () => {
    if (username.trim()) {
      setCurrentUser(username);
    }
  };

  const sendMessage = async () => {
    if (!newMessage.trim() || !currentUser) return;

    await addDoc(collection(firestoreDb, `chatRooms/${roomId}/messages`), {
      text: newMessage,
      createdAt: Timestamp.fromDate(new Date()),
      senderId: "user123", // 実際のユーザーIDに置き換え
      username: currentUser,
    });
    setNewMessage("");
  };

  return (
    <div>
      {!currentUser ? (
        <div>
          <h2>Set your username</h2>
          <input
            type="text"
            value={username}
            onChange={(e) => setUsername(e.target.value)}
          />
          <button onClick={handleSetUsername}>Set Username</button>
        </div>
      ) : (
        <div>
          <h1>Chat Room: {roomId}</h1>
          <div>
            {messages.map((msg) => (
              <div key={msg.id}>
                <p>
                  <strong>{msg.username}:</strong> {msg.text}
                </p>
                <span>{msg.createdAt.toDate().toString()}</span>
              </div>
            ))}
          </div>
          <input
            type="text"
            value={newMessage}
            onChange={(e) => setNewMessage(e.target.value)}
          />
          <button onClick={sendMessage}>Send</button>
        </div>
      )}
    </div>
  );
};

4. src/common/types/env.d.ts

declare namespace NodeJS {
  interface ProcessEnv {
    readonly NEXT_PUBLIC_FIREBASE_API_KEY: string;
    readonly NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN: string;
    readonly NEXT_PUBLIC_FIREBASE_PROJECT_ID: string;
    readonly NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET: string;
    readonly NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID: string;
    readonly NEXT_PUBLIC_FIREBASE_APP_ID: string;
    readonly NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID: string;
  }
}

5. .env.development

firebase設定を記述します
No.2538
08/22 16:46

edit