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

Next.js中浏览器返回按钮点击后组件几秒后刷新问题

Next.js 分页返回后页面自动全量刷新问题解决

核心问题排查

首先看你代码里的拼写错误:pathName应该是pathname(Next.js路由对象中路径属性为小写),大写写法会被识别为新路径,直接触发全页刷新,而非预期的shallow路由更新,这很可能是导致问题的关键原因。


方案一:实现无刷新分页(满足需求1)

通过同步状态与路由参数、拦截pop事件,确保页面始终通过shallow路由更新,避免全量刷新。

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

export default function ProductList() {
  const router = useRouter();
  // 从路由参数初始化分页状态
  const [page, setPage] = useState(() => parseInt(router.query.p) || 1);
  const [products, setProducts] = useState([]);

  // 浏览器返回/前进时,同步路由参数到page状态
  useEffect(() => {
    const currentQueryPage = parseInt(router.query.p) || 1;
    if (currentQueryPage !== page) {
      setPage(currentQueryPage);
    }
  }, [router.query.p, page]);

  // page变化时,通过shallow路由更新地址栏参数
  useEffect(() => {
    if (typeof window !== "undefined" && router.isReady) {
      const currentQueryPage = parseInt(router.query.p) || 1;
      // 避免重复推送相同参数
      if (currentQueryPage !== page) {
        router.push(
          {
            pathname: window.location.pathname, // 修正拼写错误
            query: { ...router.query, p: page }
          },
          null,
          { shallow: true }
        );
      }
    }
  }, [page, router]);

  // 根据分页参数获取商品列表
  useEffect(() => {
    const fetchProducts = async () => {
      const res = await fetch(`/api/products?p=${page}`);
      const data = await res.json();
      setProducts(data);
    };
    fetchProducts();
  }, [page]);

  // 拦截popstate事件,阻止默认全页刷新行为
  useEffect(() => {
    const cancelPopState = router.beforePopState(({ as }) => {
      const urlParams = new URLSearchParams(as.split('?')[1]);
      const newPage = urlParams.get('p');
      // 仅拦截当前页面的分页参数变化
      if (newPage && as.startsWith(window.location.pathname)) {
        setPage(parseInt(newPage) || 1);
        return false; // 阻止默认pop行为,避免全页刷新
      }
      return true;
    });

    return () => cancelPopState();
  }, [router]);

  // 分页按钮示例
  return (
    <div>
      <div className="product-list">
        {products.map(product => (
          <div key={product.id}>{product.name}</div>
        ))}
      </div>
      <button onClick={() => setPage(prev => prev - 1)} disabled={page === 1}>上一页</button>
      <button onClick={() => setPage(prev => prev + 1)}>下一页</button>
    </div>
  );
}

关键说明

  1. 修正拼写错误:pathname是Next.js路由的标准属性,错误的pathName会导致路由跳转而非shallow更新。
  2. 双向同步状态与路由:确保浏览器返回/前进时,分页状态自动跟随路由参数变化;分页状态修改时,同步更新地址栏参数。
  3. 拦截pop事件:通过router.beforePopState手动处理返回逻辑,避免触发默认的全页刷新。

方案二:允许刷新但隐藏列表切换变化(满足需求2)

如果不需要完全阻止刷新,可通过加载状态隐藏列表切换的闪烁效果,直到刷新完成:

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

export default function ProductList() {
  const router = useRouter();
  const [page, setPage] = useState(() => parseInt(router.query.p) || 1);
  const [products, setProducts] = useState([]);
  const [isLoading, setIsLoading] = useState(false);

  // 路由变化时触发加载状态
  useEffect(() => {
    const handleRouteStart = () => setIsLoading(true);
    const handleRouteEnd = () => setIsLoading(false);

    router.events.on('routeChangeStart', handleRouteStart);
    router.events.on('routeChangeComplete', handleRouteEnd);
    router.events.on('routeChangeError', handleRouteEnd);

    return () => {
      router.events.off('routeChangeStart', handleRouteStart);
      router.events.off('routeChangeComplete', handleRouteEnd);
      router.events.off('routeChangeError', handleRouteEnd);
    };
  }, [router]);

  // 获取商品列表时显示加载状态
  useEffect(() => {
    setIsLoading(true);
    const fetchProducts = async () => {
      const res = await fetch(`/api/products?p=${page}`);
      const data = await res.json();
      setProducts(data);
      setIsLoading(false);
    };
    fetchProducts();
  }, [page]);

  // 分页按钮与路由更新逻辑同方案一(需修正pathname拼写)
  // ...

  return (
    <div>
      {isLoading ? (
        <div>加载中...</div>
      ) : (
        <div className="product-list">
          {products.map(product => (
            <div key={product.id}>{product.name}</div>
          ))}
        </div>
      )}
      <button onClick={() => setPage(prev => prev - 1)} disabled={page === 1}>上一页</button>
      <button onClick={() => setPage(prev => prev + 1)}>下一页</button>
    </div>
  );
}

关于beforeunload弹出两次提示的修复

重复绑定事件会导致多次弹窗,需在组件卸载时移除监听:

useEffect(() => {
  const handleBeforeUnload = (event) => {
    event.preventDefault();
    event.returnValue = ''; // 兼容部分浏览器
  };

  window.addEventListener('beforeunload', handleBeforeUnload);

  return () => {
    window.removeEventListener('beforeunload', handleBeforeUnload);
  };
}, []);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 15:35:32