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

NextJS 13 App Router:服务端组件取数后更新子客户端组件

解决方案:NextJS 13 App Router 实现MongoDB实时排行榜更新

你的核心问题在于:服务端组件是一次性渲染的,渲染完成后User.watch()的回调在服务端运行,但无法直接更新客户端组件的状态;同时客户端不能直接调用MongoDB的watch(安全+兼容性问题),服务端也没法访问客户端Context。

推荐用**Server-Sent Events(SSE)**实现服务端到客户端的实时推送,这是最适合单向实时更新场景的方案,步骤如下:

1. 创建SSE API路由

新建app/api/leaderboard-updates/route.ts,在这个服务端路由里建立MongoDB的change stream,通过SSE把更新推给客户端:

import { connectToDatabase, User } from '@/lib/db';
import { NextResponse } from 'next/server';

export async function GET() {
  await connectToDatabase();
  
  const stream = new ReadableStream({
    async start(controller) {
      // 先发送初始用户数据
      const initialUsers = await User.find({}).lean();
      const formattedInitial = initialUsers.map(u => ({
        uuid: u._id.toString(),
        name: u.name,
        money: u.money,
        online: u.online,
      }));
      controller.enqueue(`data: ${JSON.stringify({ type: 'initial', data: formattedInitial })}\n\n`);

      // 监听MongoDB数据变化
      const changeStream = User.watch();
      changeStream.on('change', async () => {
        // 获取最新用户列表(也可以根据change事件做局部更新优化性能)
        const latestUsers = await User.find({}).lean();
        const formatted = latestUsers.map(u => ({
          uuid: u._id.toString(),
          name: u.name,
          money: u.money,
          online: u.online,
        }));
        controller.enqueue(`data: ${JSON.stringify({ type: 'update', data: formatted })}\n\n`);
      });

      // 连接关闭时清理资源
      return () => {
        changeStream.close();
        controller.close();
      };
    },
  });

  return new NextResponse(stream, {
    headers: {
      'Content-Type': 'text/event-stream',
      'Cache-Control': 'no-cache',
      'Connection': 'keep-alive',
    },
  });
}

2. 修改客户端Leaderboard组件

在客户端组件里通过EventSource监听SSE连接,收到更新后同步本地状态:

"use client";

import { useState, useEffect } from 'react';
import { UserDAO } from '@/models/user';

interface Props {
  initialUsers: UserDAO[];
}

export default function Leaderboard(props: Props) {
  const [users, setUsers] = useState(props.initialUsers.filter(it => !it.vip));

  useEffect(() => {
    const eventSource = new EventSource('/api/leaderboard-updates');
    
    eventSource.onmessage = (event) => {
      const payload = JSON.parse(event.data);
      // 处理初始数据或更新数据
      if (payload.type === 'initial' || payload.type === 'update') {
        const filteredUsers = payload.data.filter((u: UserDAO) => !u.vip);
        setUsers(filteredUsers);
      }
    };

    eventSource.onerror = (error) => {
      console.error('SSE连接出错:', error);
      eventSource.close();
    };

    // 组件卸载时关闭连接
    return () => {
      eventSource.close();
    };
  }, []);

  return (
    <div className="block max-w-full p-4 m-0 border rounded-lg shadow bg-gray-800 border-gray-700">     
      <div className={`grid grid-cols-1 grid-rows-${users.length} gap-16 gap-y-6 grid-flow-row`}>
        {
          // 注意:先拷贝数组再排序,避免修改原数组
          [...users].sort((a, b) => b.money - a.money).map((user, index) => (
            <LeaderboardEntry key={user.uuid} user={user} place={index + 1} />
          ))
        }
      </div>
    </div>     
  );
}

3. 调整服务端Home组件

服务端组件只需要获取初始数据传给客户端,不需要再处理watch逻辑:

export default async function Home() {
  await connectToDatabase();
  const initialUsers = await getUsers();

  return (
    <Leaderboard initialUsers={initialUsers}/>
  );
}

async function getUsers(): Promise<UserDAO[]> {
  const users = await User.find({}).lean();
  return users.map(it => ({
    uuid: it._id.toString(),
    name: it.name,
    money: it.money,
    online: it.online,
  }));
}

为什么之前的方法行不通?

  • 服务端组件是一次性渲染的,渲染完成后User.watch()的回调在服务端运行,但服务端没有能力直接更新客户端组件的状态。
  • React Context的useContext只能在客户端组件中使用,服务端组件无法访问客户端的Context状态,所以没法通过Context传递更新。
  • 客户端直接调用Mongoose的watch()会暴露数据库连接信息等敏感内容,而且Mongoose的schema在客户端环境下会有兼容性问题,本身就不适合在客户端运行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 22:42:03