首页/文章/nextjs

Next.js App Router 项目,想加个进度条,但是怎么监听页面跳转

2024-12-31
12785 分钟
...

问题起因是我想给自己博客页面切换时加一个进度条。博客前端技术栈是 React,使用 Next 做了 SSR。在进度条方面,npm 上有非常成熟的库 NProgress。我们只需要调用NProgress.start()NProgress.done()即可开启和结束进度条。我们监听到页面开始、结束切换的时候,调用NProgress.start()NProgress.done()即可。但是问题是,Next 13 版本以后推出的 App Router 模式并没有给出监听路由切换的 API。

先来看看 Pages Router 模式的做法:

可以发现,在 npm 上面有一个 Next 下封装 NProgress 的库:nextjs-progressbar,它是基于 Pages Router 开发的。

在 Pages Router 模式下,可以使用router.events的 API 去监听路由切换:

import { useEffect } from 'react'
import { useRouter } from 'next/router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

export function useProgressBar() {
  const router = useRouter()
  NProgress.configure({ showSpinner: false, speed: 400 })
  useEffect(() => {
    const startHandler = () => {
      NProgress.start()
    };
    const completeHandler = () => {
      NProgress.done()
    };
    router.events.on('routeChangeStart', startHandler)
    router.events.on('routeChangeComplete', completeHandler)
  }, [])
}

是不是很方便呢,在 App Router 模式下就难搞了。

App Router 模式基于 React 的服务端组件对渲染的模式进行了更细致的划分,这看起来就很 Coooooooool,除了某些 API 不太方便之外。在 App Router 模式,没有监听路由跳转发起的 API,只能通过监听路径(usePathnameuseParams)、query 参数(useSearchParams)等方式知道路由已经发生变化了。

为了解决这个问题,这里参考了一下在 Next 的 issue:[Next 13] router.events removed?[next/navigation] Next 13: useRouter events? 下面的讨论。

监听路由开始切换,可以通过监听Link标签的click事件实现。具体操作是使用MutationObserver监听document,在子树变更时搜索所有<a>标签加上监听器。然后通过代理history.pushStatehistory.replaceState来在路由变更后关闭进度条。

借鉴 issue 中讨论的代码,这里自定义了 Hook 函数useProgressBar来实现这一点。

import { useEffect } from 'react'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'

type PushStateInput = [any, string, string | URL | null | undefined]

const handleAnchorClick = (event: MouseEvent) => {
  
  if (event.ctrlKey || event.metaKey) {
    return
  }

  const target = (event.currentTarget as HTMLAnchorElement | null)?.target
  if (target && target !== '_self') {
    return
  }
  
  const targetUrl = (event.currentTarget as HTMLAnchorElement | null)?.href
  
  if (!targetUrl || targetUrl.includes('#')) {
    return
  }

  const currentUrl = window.location.href
  if (targetUrl !== currentUrl) {
    NProgress.start()
  }
}

export const useProgressBar = () => {
  useEffect(() => {
    NProgress.configure({ showSpinner: false, speed: 400 })
  
    const handleMutation: MutationCallback = () => {
      const anchorElements: NodeListOf<HTMLAnchorElement> = document.querySelectorAll('a[href]')
      anchorElements.forEach((anchor) => anchor.addEventListener('click', handleAnchorClick))
    }
  
    
    const mutationObserver = new MutationObserver(handleMutation)
    mutationObserver.observe(document, { childList: true, subtree: true })
  
    window.history.pushState = new Proxy(window.history.pushState, {
      apply: (target, thisArg, argArray: PushStateInput) => {
        NProgress.done()
        return target.apply(thisArg, argArray)
      },
    })
    window.history.replaceState = new Proxy(window.history.replaceState, {
      apply: (target, thisArg, argArray: PushStateInput) => {
        NProgress.done()
        return target.apply(thisArg, argArray)
      },
    })
  }, [])
}

useProgressBar处理全局的Link标签,如果是使用router.push切换路由,类似地,我们也在router.push触发一次进度条好了:

import NProgress from 'nprogress'
import { useRouter } from 'next/navigation'
import { NavigateOptions } from 'next/dist/shared/lib/app-router-context.shared-runtime'

type NextNavPushArgsType = [string, NavigateOptions | undefined]

export const useProgressRouter = () => {
  const router = useRouter()
  router.push = new Proxy(router.push, {
    apply: (target, thisArg, argArray: NextNavPushArgsType) => {
      NProgress.start()
      return target.apply(thisArg, argArray)
    },
  })
  router.replace = new Proxy(router.replace, {
    apply: (target, thisArg, argArray: NextNavPushArgsType) => {
      NProgress.start()
      return target.apply(thisArg, argArray)
    },
  })
  return router
}

到这里,进度条就施工完毕了。

approuter 可以用这个nextjs-toploader 库, 在body下引用<NextTopLoader />即可

https://github.com/vercel/react-transition-progress

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
nextjs2026-04-03
揭秘 Next.js 的图像魔法
揭秘 Next.js 的“图像魔法”:不仅仅是自动压缩 你可能以为 组件只是帮你把图片体积变小了,但实际上,它更像是一个 实时的图像处理引擎 。当你把一张高清大图扔给它时,Next.js 会在服务器端(或构建时)根据用户的设备情况,动态生成最合适的图片版本。 这背后主要依赖以下几个核心机制: 现代格式的自动转码 这是性能提升最显著的一点。你上传的可能是兼容性最好的 JPG 或 PNG,但 Next.js 会检测访问者浏览器的能力。 We...
学习
nextjs2024-10-09
NextJs从0-1实现一个博客系统
Nextjs 是什么 是一个基于 React 的开源框架,用于构建生产级别的 React 应用程序。它为开发人员提供了一套丰富的工具和约定,使得创建高性能、可扩展的 Web 应用程序变得更加容易。 相关代码 Next.js 的主要特性: 服务器端渲染 (SSR): 静态站点生成 (SSG): API 路由: 文件系统路由: 图像优化: TypeScript 支持: 自定义服务器: 插件系统: 安装 使用官网推荐的方式安装,执行 ,输入项...
学习
nextjs2024-09-18
Nextjs首屏加载速度性能从80分优化到98分
google控制台的网络network去看首次加载资源请求是否有重复加载的情况 google控制台的performance来录制首屏加载过程的文件时长以及阻塞情况,避免一些首次加载无须展示的组件的优化 \ 打包size优化,@next/bundle analyzer可视化的检查页面和打包的资源大小,然后针对性去优化 选择性能指标检测工具我用的 更好的了解真实用户的体验 以上是对性能可视化和测试的工具,方便针对性优化 首先对图片,css进...
学习
nextjs2024-09-14
NextJs 14 从入门到精通
node 环境 npx、yarn、bun 工具皆可(个人偏好 ,因为它快) 初始化 next 项目 项目目录 安装 autoprefixer 及配置 postcss 从 Layout 开始 app/layout.tsx 是整个应用的主布局文件,相当于React的main.ts或App.tsx,它主要做以下几个事情: 项目 metadata 加载全局样式 globals.css 加载网络/本地字体 定义应用顶级布局 国际化 i18n 第三...
学习
nextjs2024-09-10
使用NestJS开发小程序后台
前言 前一篇写NestJS的特点,使用NestJS,开启我们的Hello World! 以及NestJS各种热更新方法,本篇会写NestJS在实际项目中的应用。(ORM、参数校验、全局错误处理等等) NestJS 中使用 TypeORM 一、安装依赖 这里假设使用 MySQL 数据库,你可以根据实际情况选择其他数据库驱动。 二、配置数据库连接 1. 在项目根目录下创建一个文件 ,内容如下: 将 、 和 替换为你的实际数据库连接信息。 2...
学习
nextjs2024-05-14
Next.js 跨域问题的各种解法
本文为稀土掘金技术社区首发签约文章,30天内禁止转载,30天后未获授权禁止转载,侵权必究! 前言 跨域是使用 Next.js 常遇到的问题,问题主要分为两类: 1. 如何调用一个跨域接口? 2. 如何实现一个跨域接口? 本篇我们会先从跨域的基础知识开始讲起,然后讲解 Next.js 下这两大类问题的各种解决方案,帮你系统解决跨域问题!快收藏、点赞这篇文章留做备忘吧! PS:系统学习 Next.js,欢迎入手小册 。基础篇、实战篇、源码篇...
学习

评论

请登录后发表评论

去登录
加载评论中...