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
相关产品推荐
相关产品推荐

