You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js集成Firestore读取量异常激增问题及优化咨询

Firestore读取量异常激增及Next.js优化方案咨询

我搭建了一个页面,需要打开Firestore集合并读取其中所有数据,且当文档发生变更时更新该文档的数据。但读取量异常激增:仅250个文档,打开页面3次,读取量却超过2000次(理论最大值应为750次)。未打开控制台,仅通过Firebase使用面板观察数据。

测试发现,打开页面一次读取量约700次;在useEffect中加计数器后,计数等于文档总数;当通过自有网站更新某一文档时(仅写入无读取),计数器会重置并从1重新计数到250,推测每次文档变更都会重新读取所有文档。

现咨询:是否有借助Next.js特性实现Firestore实时数据读取的更优方案?目前使用Firestore只能依赖'use client',无法利用服务端操作。

相关代码

"use client";
import React, { useEffect, useState } from "react";
import { auth, db } from "@/app/firebase/config";
import { collection, doc, query, where } from "firebase/firestore";
import { useCollection, useDocumentOnce } from "react-firebase-hooks/firestore";
import { useSearchParams } from "next/navigation";
import clsx from "clsx";
import CardLoading from "./CardLoading";
import { useAuthContext } from "@/app/components/SessionProvider";

const GuestCard = ({ id }: { id: string }) => {
  const { session} = useAuthContext();
  const searchParams = useSearchParams();
  const searchItem = searchParams.get("query") || "";
  const guestRef = collection(db, `guests`);
  
  const [snapshot, eventloading, eventerror, reload] = useDocumentOnce(
    doc(db, `clients/${session?.uid}`)
  );

  const rejQ = query(
    guestRef,
    where("EventID", "==", `${id}${snapshot?.data()?.EventID}`)
  );
  const [value, loading, error] = useCollection(rejQ);
  const [filtered, setFiltered] = useState([]);
  useEffect(() => {
    if (value) {
      const filteredDocs = value.docs.filter((doc) => {
        const data = doc.data();
        return data.name.toLowerCase().includes(searchItem.toLowerCase());
      });
      setFiltered(filteredDocs as any);
    }
  }, [value, searchItem]);

  return (
    <div className="h-[56rem]">
      {loading && (
        <div>
          <CardLoading />
        </div>
      )}
      {error && <div>Error: {error.toString()}</div>}
      {filtered?.map((doc: any) => {
        const data = doc.data();
        return (
          <div key={doc.id} className="text-black">
            <div className="w-full rounded-md shadow p-2 px-4 my-2">
              <div className="flex flex-row justify-between">
                <div className="flex flex-col gap-1">
                  <p className="font-bold text-[#1C1C21]">{data.name}</p>
                  <div className="flex flex-col items-start gap-1 text-[#3D404A] text-sm font-light">
                    <div className="flex flex-row gap-1">
                      <p>Phone</p>
                      <p className="text-sm"> {data.number}</p>
                    </div>

                    <div className="flex flex-row gap-1">
                      <p className="text-sm text-[#3D404A]">Status</p>
                      <p>{data.status}</p>
                    </div>
                  </div>
                </div>

                <div className="pt-2  h-[2rem] flex justify-center items-center w-[5rem]">
                  <p
                    className={clsx(
                      "bg-[#74C365] rounded-lg p-1 px-2 font-light w-full flex justify-center",
                      {
                        "bg-[#74C365]": data.reply === "yes",
                        "bg-[#FF6961]": data.reply === "no",
                        "bg-[#FFC107]": data.status === "failed"
                      }
                    )}
                  >
                    {data.reply}
                  </p>
                </div>
              </div>
            </div>
          </div>
        );
      })}
    </div>
  );
};

export default GuestCard;

优化方案

一、解决当前客户端读取量过高的核心问题

  • 稳定查询实例,避免重复监听:当前代码中rejQ会在组件每次渲染时重新创建,导致useCollection认为查询参数变化,重复发起实时监听。用useMemo包裹查询创建逻辑,仅在依赖项变更时生成新查询:
import { useMemo } from "react";

// ...其他代码
const rejQ = useMemo(() => {
  if (!snapshot?.data()?.EventID) return null;
  return query(
    guestRef,
    where("EventID", "==", `${id}${snapshot.data().EventID}`)
  );
}, [id, snapshot]);

const [value, loading, error] = useCollection(rejQ);

这样只有当id或client文档数据变化时,才会重新创建监听,避免无意义的重复连接。

  • 优化实时监听的更新逻辑:useCollection在文档变更时会返回整个集合快照,当前代码每次都全量过滤数据。可以在客户端维护数据缓存,仅处理变更的文档,减少不必要的状态更新和潜在的重复读取触发。

二、结合Next.js特性实现服务端+客户端混合方案

实时更新必须在客户端完成,但可以通过Next.js的服务端能力减少初始读取量:

  • 服务端预取初始数据:在App Router的页面组件中,通过服务端逻辑预取初始集合数据,传递给客户端组件,客户端仅监听后续变更:
// app/guests/[id]/page.tsx
import { db } from "@/app/firebase/config";
import { collection, query, where, getDocs, doc, getDoc } from "firebase/firestore";
import GuestCard from "./GuestCard";
import { getAuth, verifyIdToken } from "firebase/auth";
import { cookies } from "next/headers";

export default async function GuestPage({ params }: { params: { id: string } }) {
  // 服务端验证用户身份,获取uid
  const authCookie = cookies().get("__session")?.value;
  const auth = getAuth();
  const decodedToken = await verifyIdToken(auth, authCookie || "");
  const uid = decodedToken.uid;

  // 预取client文档和guest集合数据
  const clientDoc = await getDoc(doc(db, `clients/${uid}`));
  const eventId = `${params.id}${clientDoc.data()?.EventID}`;
  const q = query(collection(db, "guests"), where("EventID", "==", eventId));
  const querySnapshot = await getDocs(q);
  const initialGuests = querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }));

  return <GuestCard id={params.id} initialGuests={initialGuests} />;
}

客户端组件接收初始数据后,仅用useCollection监听后续变更:

const GuestCard = ({ id, initialGuests }: { id: string; initialGuests: any[] }) => {
  const [guests, setGuests] = useState(initialGuests);
  // ...其他逻辑

  useEffect(() => {
    if (value) {
      // 仅更新变更后的文档数据,而非全量替换
      const updatedGuests = value.docs.map(doc => ({ id: doc.id, ...doc.data() }));
      setGuests(updatedGuests);
    }
  }, [value]);

  // 基于guests数据做过滤渲染
};

这种方式让初始数据从服务端获取,客户端只处理实时增量更新,大幅减少客户端初始读取量。

  • 增量静态再生(ISR)适配:如果数据不需要秒级实时更新,可以用ISR定期生成静态页面,同时客户端保留实时监听,平衡性能与实时性。

三、其他补充优化

  • 客户端缓存强化:结合SWR或React Query等缓存库,对Firestore数据做客户端缓存,避免重复读取。react-firebase-hooks本身带缓存,但稳定的查询实例是缓存生效的前提。
  • Firestore索引优化:确保EventID字段已创建复合索引,避免查询时的隐式索引创建带来的额外性能开销。
  • 组件渲染控制:用React.memo包裹GuestCard组件,避免不必要的重渲染导致监听重复初始化。

内容的提问来源于stack exchange,提问作者Genio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 01:09:53