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

Next.js 13 App目录下服务端状态推送至客户端组件的实现方案

解决方案:Next.js 13 App 目录下主机状态卡片异步更新实现

针对你的需求,这里提供三种实用方案,均兼容客户端组件限制,同时优化多主机场景下的性能:


方案1:批量API请求 + 客户端全局状态管理(适合多主机场景)

通过一次请求获取所有主机状态,减少HTTP请求数,提升整体性能。

1. 创建批量查询API路由

新建 app/api/check-hosts/route.ts:

import { NextResponse } from 'next/server';

async function isAvailable(client: string): Promise<boolean> {
  const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
  await sleep(1000 + Math.random() * 1000);
  return Math.random() > 0.5;
}

export async function POST(request: Request) {
  const { hosts } = await request.json();
  
  // 并行处理所有主机检测,提升效率
  const results = await Promise.all(
    hosts.map(async (host: string) => ({
      host,
      available: await isAvailable(host)
    }))
  );

  return NextResponse.json(results);
}

2. 新增客户端状态容器组件

新建 app/clients/ClientsContainer.tsx:

"use client";

import { useState, useEffect } from 'react';
import ClientCard from './client-card';

type ClientsContainerProps = {
  hosts: string[];
};

export default function ClientsContainer({ hosts }: ClientsContainerProps) {
  const [hostStatuses, setHostStatuses] = useState<Record<string, boolean | undefined>>({});

  useEffect(() => {
    const fetchStatuses = async () => {
      const res = await fetch('/api/check-hosts', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ hosts })
      });
      const results = await res.json();
      
      // 转换为状态映射表
      const statusMap = results.reduce((acc: Record<string, boolean>, item: { host: string; available: boolean }) => {
        acc[item.host] = item.available;
        return acc;
      }, {});
      setHostStatuses(statusMap);
    };

    fetchStatuses();
  }, [hosts]);

  return (
    <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
      {hosts.map((client) => (
        <div key={client} className="col-span-1">
          <ClientCard
            label={client}
            available={hostStatuses[client]}
          />
        </div>
      ))}
    </div>
  );
}

3. 修改服务端页面组件

更新 app/clients/page.tsx:

import fs from "fs";
import path from "path";
import { load } from "js-yaml";
import ClientsContainer from "./ClientsContainer";

type AnsibleInventory = {
  probes?: {
    hosts: Record<string, unknown>;
  };
};

async function getInventory() {
  const inventoryFile = path.join(
    __dirname,
    "..",
    "..",
    "..",
    "..",
    "..",
    "example",
    "inventory",
    "hosts.yml"
  );
  const inventory = load(
    fs.readFileSync(inventoryFile, "utf8")
  ) as AnsibleInventory;

  return inventory;
}

export default async function Clients() {
  const inventory = await getInventory();
  const hosts = inventory.probes?.hosts ? Object.keys(inventory.probes.hosts) : [];

  return (
    <div>
      <h1>Clients</h1>
      {hosts.length === 0 ? (
        <p>No clients found</p>
      ) : (
        <ClientsContainer hosts={hosts} />
      )}
    </div>
  );
}

方案2:单主机请求 + SWR(支持单独刷新)

适合需要单独刷新某台主机状态的场景,SWR自动缓存请求避免重复调用。

1. 创建单主机检测API路由

新建 app/api/check-host/[host]/route.ts:

import { NextResponse } from 'next/server';

async function isAvailable(client: string): Promise<boolean> {
  const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
  await sleep(1000 + Math.random() * 1000);
  return Math.random() > 0.5;
}

export async function GET(request: Request, { params }: { params: { host: string } }) {
  const { host } = params;
  const available = await isAvailable(host);
  return NextResponse.json({ available });
}

2. 修改客户端卡片组件

更新 app/clients/client-card.tsx:

"use client";

import { Card, CardBody } from "../components";
import { Chip } from "@material-tailwind/react";
import useSWR from 'swr';

const fetcher = (url: string) => fetch(url).then(res => res.json());

export type ClientProps = {
  label: string;
};

export default function ClientCard({ label }: ClientProps) {
  const { data, error } = useSWR(`/api/check-host/${label}`, fetcher);

  // 错误时默认标记为离线
  const available = error ? false : data?.available;

  return (
    <Card title={label}>
      <CardBody>
        <h4>{label}</h4>
        <Chip
          color={available === undefined ? "gray" : available ? "green" : "red"}
          className="mt-2"
          value={available === undefined ? "Checking" : available ? "Online" : "Offline"}
        />
      </CardBody>
    </Card>
  );
}

3. 服务端页面组件

直接复用你原有的 page.tsx,去掉内部的 isAvailable 函数即可。


方案3:Server Actions(Next.js 13+ 推荐)

无需单独创建API路由,直接通过Server Actions在服务端执行检测逻辑,代码更简洁安全。

1. 定义Server Action

新建 app/clients/actions.ts:

"use server";

export async function checkHosts(hosts: string[]) {
  const isAvailable = async (client: string): Promise<boolean> => {
    const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
    await sleep(1000 + Math.random() * 1000);
    return Math.random() > 0.5;
  };

  const results = await Promise.all(
    hosts.map(async host => ({ host, available: await isAvailable(host) }))
  );

  return results;
}

2. 修改客户端容器组件

更新 app/clients/ClientsContainer.tsx:

"use client";

import { useState, useEffect } from 'react';
import ClientCard from './client-card';
import { checkHosts } from './actions';

type ClientsContainerProps = {
  hosts: string[];
};

export default function ClientsContainer({ hosts }: ClientsContainerProps) {
  const [hostStatuses, setHostStatuses] = useState<Record<string, boolean | undefined>>({});

  useEffect(() => {
    const fetchStatuses = async () => {
      const results = await checkHosts(hosts);
      const statusMap = results.reduce((acc, item) => {
        acc[item.host] = item.available;
        return acc;
      }, {} as Record<string, boolean>);
      setHostStatuses(statusMap);
    };

    fetchStatuses();
  }, [hosts]);

  return (
    <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
      {hosts.map((client) => (
        <div key={client} className="col-span-1">
          <ClientCard
            label={client}
            available={hostStatuses[client]}
          />
        </div>
      ))}
    </div>
  );
}

3. 服务端页面组件

和方案1的 page.tsx 完全一致。


方案选择建议

  • 主机数量较多时:优先选方案1或方案3,批量处理减少请求数,提升性能
  • 需要单独刷新主机状态时:选方案2,借助SWR实现缓存和单独刷新
  • 追求代码简洁性:选方案3,无需维护额外API路由

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 21:47:18