{"id":24230342,"url":"https://github.com/run4w4y/nextjs-router-events","last_synced_at":"2025-10-18T17:08:05.483Z","repository":{"id":188073761,"uuid":"677868252","full_name":"run4w4y/nextjs-router-events","owner":"run4w4y","description":"A router events alternative for Next 13+ with app directory","archived":false,"fork":false,"pushed_at":"2025-02-12T07:30:04.000Z","size":25,"stargazers_count":39,"open_issues_count":2,"forks_count":4,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-31T10:10:18.365Z","etag":null,"topics":["app-directory","navigation","nextjs","router-events","typescript"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/run4w4y.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-08-12T23:04:45.000Z","updated_at":"2025-02-21T00:34:29.000Z","dependencies_parsed_at":"2025-02-28T18:14:52.475Z","dependency_job_id":"cb1a6d90-4590-46ad-9569-0984769bd057","html_url":"https://github.com/run4w4y/nextjs-router-events","commit_stats":null,"previous_names":["run4w4y/nextjs-router-events"],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run4w4y%2Fnextjs-router-events","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run4w4y%2Fnextjs-router-events/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run4w4y%2Fnextjs-router-events/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/run4w4y%2Fnextjs-router-events/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/run4w4y","download_url":"https://codeload.github.com/run4w4y/nextjs-router-events/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247648978,"owners_count":20972945,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["app-directory","navigation","nextjs","router-events","typescript"],"created_at":"2025-01-14T13:19:27.951Z","updated_at":"2025-10-18T17:08:00.422Z","avatar_url":"https://github.com/run4w4y.png","language":"TypeScript","funding_links":[],"categories":["TypeScript"],"sub_categories":[],"readme":"# nextjs-router-events\nA router events alternative for Next.js 13+ with app directory with the ability to prevent user navigation.\n\n# Disclaimer\nInitially I wrote this as a workaround for my project needs, and it worked fine in my case. However,\nthat does not mean it is production-ready. As such, do NOT use this package in production, unless you absolutely must. In case you do, you do so at your own risk.\n\nIf you've found a bug or have suggestions regarding this package, feel free to open an issue/pull request.\n\n# Motivation\nBefore app directory, Next.js provided developers with the ability to not only track route changes, but also to prevent the user from navigating to another page with router events. Unfortunately, I have not found an official solution for either of those. While I have seen some other community provided workarounds for the former use-case, I have not found one for the latter, so here we are. \n\nI certainly hope we get official support for both of these use-cases and this package becomes redundant, but meanwhile you can use this.\n\n# Installation\nInstall the package from npm\n```\nnpm install nextjs-router-events\n```\n\n# Caveats\nWhat this package does, is basically attach `click` event listeners to all `a` nodes in the DOM, and from there handle the necessary logic for the route change events. As it is, the package will be treating **all** `a` node clicks as events of navigation, whether it is an anchor link, an external link or an internal link. It also does not check what the `a` node `target` attribute is, so route change events will be triggered for `target=\"_blank\"` as well (except `routeChangeEnd`). As such, you should be keeping that in mind when using this package with things like `nprogress`.\n\nThis package also only handles `router.push`, so all other `router` methods such as `back`, `forward`, `refresh` and etc are not covered.\nI do plan on covering more `router` methods where it is possible to do so, as well as offering some sort of opt-out for the `a` nodes in the DOM, however at the moment it is what it is.\n\n# Setup\nThe package exports `RouteChangesProvider`, use it inside your `layout` like so\n```typescript\n// layout.tsx\nimport React from 'react'\nimport { RouteChangesProvider } from 'nextjs-router-events'\n\nconst Layout = ({ children }: { children: React.ReactNode }) =\u003e {\n    return (\n        \u003cRouteChangesProvider\u003e\n            {children}\n        \u003c/RouteChangesProvider\u003e\n    )\n}\n\nexport default Layout\n```\n\nAfter that, if you're using `router.push` in your application you probably also want to replace your `useRouter` from `next/navigation` usage with `useRouter` exported by this package\n```typescript\nimport { useRouter } from 'nextjs-router-events'\n``` \n\nIf you find it tedious to go through your imports, you could probably use `resolve.alias` in your webpack configuration to just alias `next/navigation` to something that re-exports all of its contents, except for the `useRouter` and instead exports the one from this package.\n\n# API\n\nAside from the `useRouter` (which has the exact same API as the one from `next/navigation`) and `RouteChangesProvider` (whose only prop is `children`), the package exports `useRouteChangeEvents` hook. \n\n## `useRouteChangeEvents` props\n- `onBeforeRouteChange?: (target: string) =\u003e boolean | void` - optional, this function will be called every time **before** the navigation takes place. It takes one argument: the target (for example, `href` attribute of the `a` tag the user clicked) and should return either `undefined` or a `boolean`. If the function returned `true` or `undefined`, the navigation proceeds. If the function returned `false`, the navigation is prevented until `allowRouteChange` (read further) is called.\n- `onRouteChangeStart?: (target: string) =\u003e void` - optional, this function will be called every time **after** the navigation has already started. Similarly to `onBeforeRouteChange`, the function also receives `target` as its argument.\n- `onRouteChangeComplete?: (target: HistoryURL) =\u003e void` - optional as well, this will be called every time **after** the navigation has ended. This function also receives `target`, but now instead of just `string` it has the type signature of `string | URL | null | undefined`. \n\n## `useRouteChangeEvents` return value\nIt returns an object that only contains the `allowRouteChange: () =\u003e void` function mentioned before. You should only use it after preventing a user navigation, in case there wasn't any navigation prevented prior to calling it nothing really will happen, although you're going to receive a warning in your console.\n\n# Examples\n\n## Preventing user from leaving a page with unsaved changes\nDefine a `useLeaveConfirmation` hook like this \n```typescript\nimport { useCallback, useState } from \"react\"\nimport { useRouteChangeEvents } from \"nextjs-router-events\"\nimport useBeforeUnload from './useBeforeUnload' // read further for an explanation\nimport { \n  AlertDialog, \n  AlertDialogCancel, \n  AlertDialogContent, \n  AlertDialogDescription, \n  AlertDialogFooter, \n  AlertDialogHeader,\n  AlertDialogTitle,\n  AlertDialogAction,\n} from \"@/components/ui/alertDialog\" // this is just radix-ui Alert Dialog, replace it with whatever fits your project\n\nconst useLeaveConfirmation = (shouldPreventRouteChange: boolean) =\u003e {\n  const [showConfirmationDialog, setShowConfirmationDialog] = useState(false)\n  const onBeforeRouteChange = useCallback(() =\u003e {\n    if (shouldPreventRouteChange) {\n      setShowConfirmationDialog(true)\n      return false\n    }\n\n    return true\n  }, [shouldPreventRouteChange])\n\n  const { allowRouteChange } = useRouteChangeEvents({ onBeforeRouteChange })\n  // this is technically unrelated to this package, but probably still is something you might want to do\n  useBeforeUnload(shouldPreventRouteChange)\n\n  return {\n    confirmationDialog: (\n      \u003cAlertDialog \n        open={showConfirmationDialog} \n        onOpenChange={setShowConfirmationDialog}\n      \u003e\n        \u003cAlertDialogContent\u003e\n          \u003cAlertDialogHeader\u003e\n            \u003cAlertDialogTitle\u003e\n              You have unsaved changes\n            \u003c/AlertDialogTitle\u003e\n            \u003cAlertDialogDescription\u003e\n              Are you sure you want to leave? \n              All the unsaved changes will be lost.\n            \u003c/AlertDialogDescription\u003e\n          \u003c/AlertDialogHeader\u003e\n          \u003cAlertDialogFooter\u003e\n            \u003cAlertDialogCancel\u003e\n              Cancel\n            \u003c/AlertDialogCancel\u003e\n            \u003cAlertDialogAction onClick={() =\u003e {\n              allowRouteChange()\n            }}\u003e\n              Proceed\n            \u003c/AlertDialogAction\u003e\n          \u003c/AlertDialogFooter\u003e\n        \u003c/AlertDialogContent\u003e\n      \u003c/AlertDialog\u003e\n    )\n  }\n}\n\nexport default useLeaveConfirmation\n```\n\nNow, you can use this hook in a component like this (this is not actual working code, just an example)\n```typescript\nimport useLeaveConfirmation from '@/hooks/useLeaveConfirmation'\nimport { useStore } from '@/store'\n\nconst Component = () =\u003e {\n    const store = useStore() // your hypothetical application state\n    // below replace `store.isDirty` with whatever logic to determine whether or not your application state has been modified by the user\n    const { confirmationDialog } = useLeaveConfirmation(store.isDirty()) \n\n    // render the confirmationDialog somewhere\n    return (\n        \u003c\u003e\n            ...\n            {confirmationDialog}\n            ...\n        \u003c/\u003e\n    )\n}\n```\n\n### Note\nSince you are trying to prevent the user from leaving, you probably also want to cover the cases where user \"leaves\" using browser-native navigation methods such as back button or page refresh. In case you do, you might as well use `useBeforeUnload` hook within `useLeaveConfirmation`. The hook can be defined like this:\n```typescript\nimport { useEffect } from \"react\"\n\n// NOTE: although there is a message argument, you really should not be relying on it, as most, if not all, modern browsers completely ignore it anyways\nconst useBeforeUnload = (shouldPreventUnload: boolean, message?: string) =\u003e {\n  useEffect(() =\u003e {\n    const abortController = new AbortController()\n\n    if (shouldPreventUnload)\n      window.addEventListener('beforeunload', (ev) =\u003e {\n        ev.preventDefault()\n\n        return (ev.returnValue = message ?? '')\n      }, { capture: true, signal: abortController.signal })\n\n    return () =\u003e abortController.abort()\n  }, [shouldPreventUnload, message])\n}\n\nexport default useBeforeUnload\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frun4w4y%2Fnextjs-router-events","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frun4w4y%2Fnextjs-router-events","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frun4w4y%2Fnextjs-router-events/lists"}