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

Next.js App Router中调用router.refresh()时如何保留滚动状态

解决Next.js 13 App Router中router.refresh()重置滚动位置的问题

针对你遇到的问题,提供三种可行的解决方案:


方案一:手动保存并恢复滚动位置

在调用router.refresh()前后,手动记录和恢复列表容器的滚动位置,是最直接的临时修复方案:

'use client';

import { useRouter } from 'next/navigation';
import { useState, useEffect, useRef } from 'react';

export default function StarButton({ itemId }: { itemId: string }) {
  const router = useRouter();
  // 绑定到列表容器的DOM引用
  const listContainerRef = useRef<HTMLDivElement>(null);
  const [savedScrollTop, setSavedScrollTop] = useState(0);

  const handleStarToggle = async () => {
    // 1. 点击星标前,保存当前滚动位置
    if (listContainerRef.current) {
      setSavedScrollTop(listContainerRef.current.scrollTop);
    }

    // 2. 执行数据库更新逻辑
    await fetch(`/api/toggle-star/${itemId}`, { method: 'POST' });

    // 3. 触发服务端组件刷新
    router.refresh();
  };

  // 4. 刷新完成后恢复滚动位置
  useEffect(() => {
    if (listContainerRef.current && savedScrollTop > 0) {
      listContainerRef.current.scrollTop = savedScrollTop;
      // 重置状态,避免路由变化时重复触发
      setSavedScrollTop(0);
    }
  }, [router, savedScrollTop]);

  return (
    <>
      {/* 列表容器,绑定ref */}
      <div ref={listContainerRef} className="h-[600px] overflow-y-auto">
        {/* 你的卡片列表 */}
      </div>
      <button onClick={handleStarToggle} className="cursor-pointer">⭐</button>
    </>
  );
}

方案二:改用客户端局部更新(推荐)

放弃router.refresh(),改用客户端数据缓存工具(如SWR/React Query)实现局部列表更新,完全避免页面级刷新带来的滚动重置:

以SWR为例:

'use client';

import useSWR from 'swr';

// 数据获取函数
const fetchItems = () => fetch('/api/items').then(res => res.json());

export default function ItemList() {
  // 用SWR管理列表数据缓存
  const { data: items, mutate } = useSWR('items-list', fetchItems);

  const handleStarToggle = async (itemId: string) => {
    // 1. 先更新数据库
    await fetch(`/api/toggle-star/${itemId}`, { method: 'POST' });
    // 2. 触发缓存失效,重新获取最新列表数据
    await mutate();
    // 可选:如果需要即时视觉反馈,可以手动调整items数组顺序,把星标项移到顶部
  };

  if (!items) return <div>加载中...</div>;

  return (
    <div className="h-[600px] overflow-y-auto">
      {items.map(item => (
        <div key={item.id} className="card">
          <button onClick={() => handleStarToggle(item.id)}>
            {item.isStarred ? '⭐' : '☆'}
          </button>
          {/* 卡片内容 */}
        </div>
      ))}
    </div>
  );
}

这种方案的优势是用户体验流畅,没有页面刷新的闪烁,也不会影响滚动位置。


方案三:使用revalidateTag精准刷新

如果你的服务端组件数据获取使用了Tagged Cache,可以用revalidateTag替代router.refresh(),只刷新指定标签的缓存数据,避免全页刷新:

服务端组件(列表页面):

// app/items/page.tsx
export default async function ItemsPage() {
  // 给数据请求标记标签
  const items = await fetch('/api/items', {
    next: { tags: ['user-items'] }
  }).then(res => res.json());

  // 将数据传递给客户端组件渲染
  return <ItemList items={items} />;
}

客户端组件(星标按钮):

'use client';

import { revalidateTag } from 'next/cache';

export default function StarButton({ itemId }: { itemId: string }) {
  const handleStarToggle = async () => {
    await fetch(`/api/toggle-star/${itemId}`, { method: 'POST' });
    // 仅重新验证标记为'user-items'的缓存数据
    revalidateTag('user-items');
  };

  return <button onClick={handleStarToggle}>⭐</button>;
}

revalidateTag只会触发对应标签数据的重新获取,不会导致页面滚动位置重置,同时保持服务端组件的数据新鲜度。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 15:37:03