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

Next.js 15仪表盘多组件带筛选的API数据获取问题咨询

问题描述

我正在开发基于Next.js 15(App Router)的Web应用,仪表盘包含多个展示API数据的组件(widget),每个组件对应独立数据源API。原本各组件用Server Component获取数据,加载时显示骨架屏,但每个API支持多筛选条件,且每个组件配有筛选UI——筛选是客户端交互,Server Component不支持,所以把筛选UI做成了Client Component,现在需要解决如何让Server Component获取筛选条件并重新获取数据的问题。

已尝试的方案

  1. URL查询参数传递筛选条件
    把筛选条件存在URL查询参数里,Server Component通过searchParams props获取。但设置参数时,用户操作到参数生效有轻微延迟,交互卡顿。

Server Component代码:

import fetchData from "@/lib/actions/fetch"

export default async function TotalEarningWidget({searchParams}) {
  const currentSearchParams = await searchParams

  const apiSearchParams = new URLSearchParams()
  apiSearchParams.append('filter', currentSearchParams.filter)
  
  // 基于fetch封装的API调用工具
  const totalEarningsData = await fetchData(`/total-earnings?${apiSearchParams.toString()}`)
  
  return <div>{/* 组件内容 */}</div>
}

Client Component(筛选UI)代码:

"use client"

import { useRouter, useSearchParams } from "next/navigation"

export default function FilterComponent() {
    const { replace } = useRouter()
    const currentSearchParams = useSearchParams()

    return (
        <div>
            <select
                defaultValue={currentSearchParams.get("filter")?.toString()}
                onChange={e => replace(`?filter=${e.target.value}`)}
            >
                <option value="1">筛选选项1</option>
                <option value="2">筛选选项2</option>
                <option value="3">筛选选项3</option>
            </select>
        </div>
    )
}

  1. 客户端状态管理+Server Actions
    用客户端useState管理筛选条件,同步到URL避免刷新丢失数据,通过Server Actions在客户端获取数据(隐藏真实API地址)。方案可行,但首屏加载速度比服务端获取慢,想确认是否因切换到客户端获取导致,或有其他问题。

Client Component代码:

"use client"

import { useSearchParams } from "next/navigation"
import { useEffect, useState } from "react"

export default function TotalEarningWidget({ searchParams }) {
    const [data, setData] = useState()
    const currentSearchParams = useSearchParams()

    const [appliedFilters, setAppliedFilters] = useState({
        filter: currentSearchParams.get("filter")?.toString(),
    })

    useEffect(() => {
        const fetchedData = fetchDataAction(appliedFilters.filter)
        setData(fetchedData)
    }, [appliedFilters])

    const handleFilterChange = filterToSet => {
        setAppliedFilters({ filter: filterToSet })
    }

    return <div>{/* 组件内容 */}</div>
}
解决方案

优化URL查询参数方案(解决卡顿问题)

1. 优化路由更新逻辑

用usePathname和URLSearchParams组合构建新路由,避免手动拼接字符串,同时添加scroll: false减少不必要的滚动操作:

"use client"
import { useRouter, useSearchParams, usePathname } from "next/navigation"

export default function FilterComponent() {
  const router = useRouter()
  const pathname = usePathname()
  const searchParams = useSearchParams()

  const handleFilterChange = (e) => {
    const newParams = new URLSearchParams(searchParams)
    newParams.set("filter", e.target.value)
    // 用replace更新路由,添加scroll: false避免页面滚动
    router.replace(`${pathname}?${newParams.toString()}`, { scroll: false })
  }

  return (
    <div>
      <select
        defaultValue={searchParams.get("filter")}
        onChange={handleFilterChange}
      >
        <option value="1">筛选选项1</option>
        <option value="2">筛选选项2</option>
        <option value="3">筛选选项3</option>
      </select>
    </div>
  )
}

2. 预加载数据+骨架屏优化

在父组件中用Suspense包裹Server Component,让路由更新时立即显示骨架屏,减少卡顿感知:

// Dashboard页面(父组件)
import { Suspense } from 'react'
import TotalEarningWidget from './TotalEarningWidget'
import FilterComponent from './FilterComponent'
import Skeleton from './Skeleton'

export default async function Dashboard() {
  return (
    <div>
      <FilterComponent />
      <Suspense fallback={<Skeleton />}>
        <TotalEarningWidget />
      </Suspense>
    </div>
  )
}

3. 启用动态参数预渲染

在next.config.js中配置dynamicParams: true,让Next.js预渲染常见的筛选组合,加快路由切换速度:

// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    appDir: true,
  },
  dynamicParams: true,
}

module.exports = nextConfig

优化客户端状态+Server Actions方案(解决首屏慢问题)

1. 拆分Server/Client组件,首屏数据服务端获取

将组件拆分为Server Component(负责首屏数据加载)和Client Component(负责筛选交互),兼顾首屏速度和交互流畅度:

// Server Component: TotalEarningWidget.server.js
import fetchData from "@/lib/actions/fetch"
import TotalEarningWidgetClient from "./TotalEarningWidget.client"

export default async function TotalEarningWidget({ searchParams }) {
  const apiSearchParams = new URLSearchParams()
  apiSearchParams.append('filter', searchParams.filter || '1')
  
  const initialData = await fetchData(`/total-earnings?${apiSearchParams.toString()}`)
  
  return <TotalEarningWidgetClient initialData={initialData} />
}
// Client Component: TotalEarningWidget.client.js
"use client"
import { useSearchParams, useRouter } from "next/navigation"
import { useState, useEffect } from "react"
import { fetchDataAction } from "@/lib/actions/fetch"

export default function TotalEarningWidgetClient({ initialData }) {
  const [data, setData] = useState(initialData)
  const searchParams = useSearchParams()
  const [appliedFilters, setAppliedFilters] = useState({
    filter: searchParams.get("filter") || '1',
  })
  const router = useRouter()
  const pathname = usePathname()

  useEffect(() => {
    // 筛选变化时调用Server Actions获取数据
    const fetchNewData = async () => {
      const newData = await fetchDataAction(appliedFilters.filter)
      setData(newData)
    }
    fetchNewData()
  }, [appliedFilters])

  const handleFilterChange = (filterToSet) => {
    setAppliedFilters({ filter: filterToSet })
    // 同步更新URL
    const newParams = new URLSearchParams()
    newParams.set("filter", filterToSet)
    router.replace(`${pathname}?${newParams.toString()}`, { scroll: false })
  }

  return (
    <div>
      <select
        defaultValue={appliedFilters.filter}
        onChange={(e) => handleFilterChange(e.target.value)}
      >
        <option value="1">筛选选项1</option>
        <option value="2">筛选选项2</option>
        <option value="3">筛选选项3</option>
      </select>
      {/* 渲染数据 */}
      <div>{data?.total}</div>
    </div>
  )
}

2. 优化Server Actions缓存策略

给Server Actions添加缓存标签和过期时间,避免重复请求相同筛选条件的数据:

// lib/actions/fetch.js
"use server"
import { revalidateTag } from 'next/cache'

export async function fetchDataAction(filter) {
  const res = await fetch(`https://your-api.com/total-earnings?filter=${filter}`, {
    next: {
      tags: ['total-earnings'], // 添加缓存标签
      revalidate: 60, // 60秒自动重新验证缓存
    },
  })
  if (!res.ok) throw new Error('Failed to fetch data')
  return res.json()
}

// 需要强制刷新缓存时调用
export async function revalidateTotalEarnings() {
  revalidateTag('total-earnings')
}

3. 减少不必要的重渲染

用useCallback和memo优化组件,避免筛选变化时的无意义重渲染:

"use client"
import { useSearchParams, useRouter } from "next/navigation"
import { useState, useEffect, useCallback, memo } from "react"
import { fetchDataAction } from "@/lib/actions/fetch"

const TotalEarningWidgetClient = memo(({ initialData }) => {
  const [data, setData] = useState(initialData)
  const searchParams = useSearchParams()
  const [appliedFilters, setAppliedFilters] = useState({
    filter: searchParams.get("filter") || '1',
  })
  const router = useRouter()
  const pathname = usePathname()

  useEffect(() => {
    const fetchNewData = async () => {
      const newData = await fetchDataAction(appliedFilters.filter)
      setData(newData)
    }
    fetchNewData()
  }, [appliedFilters])

  const handleFilterChange = useCallback((filterToSet) => {
    setAppliedFilters({ filter: filterToSet })
    const newParams = new URLSearchParams()
    newParams.set("filter", filterToSet)
    router.replace(`${pathname}?${newParams.toString()}`, { scroll: false })
  }, [router, pathname])

  return (
    <div>
      <select
        defaultValue={appliedFilters.filter}
        onChange={(e) => handleFilterChange(e.target.value)}
      >
        <option value="1">筛选选项1</option>
        <option value="2">筛选选项2</option>
        <option value="3">筛选选项3</option>
      </select>
      <div>{data?.total}</div>
    </div>
  )
})

export default TotalEarningWidgetClient

方案选择建议

  • 如果需要SEO友好、首屏速度快,优先选优化后的URL查询参数方案,适合公开数据的仪表盘。
  • 如果需要无路由刷新的流畅交互,且数据不需要强SEO,选拆分Server/Client组件的方案,兼顾首屏性能和用户体验。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 19:19:50