navlens
v0.4.0
Published
Track client-side navigation history with timestamps across React, Vue, Svelte, and more
Readme
NavLens
Install
npm install navlens
# or
pnpm add navlens
# or
yarn add navlensUsage
Next.js (App Router)
Add the tracker to your root layout:
// app/providers.tsx
"use client";
import { NavigationTracker as ReactNavigationTracker, useNextAdapter } from "navlens/next";
import { Suspense } from "react";
function NavigationTracker() {
const adapter = useNextAdapter();
return <ReactNavigationTracker adapter={adapter} />;
}
export function Providers({ children }: { children: React.ReactNode }) {
return (
<>
<Suspense fallback={null}>
<NavigationTracker />
</Suspense>
{children}
</>
);
}// app/layout.tsx
import { Providers } from "./providers";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Providers>{children}</Providers>
</body>
</html>
);
}Use in any page:
"use client";
import { useRouter } from "next/navigation";
import { getPreviousPath } from "navlens";
export default function ProductDetailPage() {
const router = useRouter();
function handleBack() {
const prev = getPreviousPath();
if (prev) router.back();
else router.push("/");
}
return <button onClick={handleBack}>← Back</button>;
}
React Router
// src/App.tsx
import { Routes, Route } from "react-router-dom";
import {
NavigationTracker as ReactNavigationTracker,
useReactRouterAdapter,
} from "navlens/react-router";
export default function App() {
const adapter = useReactRouterAdapter();
return (
<>
<ReactNavigationTracker adapter={adapter} />
<Routes>
<Route path="/" element={<HomePage />} />
<Route path="/products" element={<ProductsPage />} />
<Route path="/products/:id" element={<ProductDetailPage />} />
</Routes>
</>
);
}// src/pages/ProductDetailPage.tsx
import { useNavigate } from "react-router-dom";
import { getPreviousPath } from "navlens";
export default function ProductDetailPage() {
const navigate = useNavigate();
function handleBack() {
const prev = getPreviousPath();
if (prev) navigate(-1);
else navigate("/");
}
return <button onClick={handleBack}>← Back</button>;
}
Vue Router
<!-- src/App.vue -->
<script setup lang="ts">
import { useVueNavigationHistory } from "navlens/vue-router";
useVueNavigationHistory();
</script>
<template>
<RouterView />
</template><!-- src/views/ProductDetailView.vue -->
<script setup lang="ts">
import { useRouter } from "vue-router";
import { getPreviousPath } from "navlens";
const router = useRouter();
function handleBack() {
const prev = getPreviousPath();
if (prev) router.back();
else router.push("/");
}
</script>
<template>
<button @click="handleBack">← Back</button>
</template>
Nuxt
<!-- layouts/default.vue -->
<script setup lang="ts">
import { useVueNavigationHistory } from "navlens/nuxt";
useVueNavigationHistory();
</script>
<template>
<slot />
</template><!-- pages/products/[id].vue -->
<script setup lang="ts">
import { getPreviousPath } from "navlens";
const router = useRouter();
function handleBack() {
const prev = getPreviousPath();
if (prev) router.back();
else navigateTo("/");
}
</script>
Quasar
// src/boot/navlens.ts
import { boot } from "quasar/wrappers";
import { pushEntry } from "navlens/quasar";
export default boot(({ router }) => {
router.afterEach((to) => {
pushEntry(to.fullPath);
});
});Register in quasar.config.ts:
boot: ["navlens"];
SvelteKit
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import { afterNavigate } from '$app/navigation'
import { createNavigationHandler } from 'navlens/svelte'
afterNavigate(createNavigationHandler())
</script>
<slot /><!-- src/routes/products/[id]/+page.svelte -->
<script lang="ts">
import { goto } from '$app/navigation'
import { getPreviousPath } from 'navlens'
function handleBack() {
const prev = getPreviousPath()
if (prev) history.back()
else goto('/')
}
</script>
<button on:click={handleBack}>← Back</button>API
getPreviousPath(config?)
Returns the path the user was on before the current page, or undefined if there is no previous entry.
import { getPreviousPath } from "navlens";
const prev = getPreviousPath(); // e.g. '/products'getCurrentPath(config?)
Returns the most recently recorded path.
import { getCurrentPath } from "navlens";
const current = getCurrentPath(); // e.g. '/products/42'getNavHistory(config?)
Returns the full navigation history array, newest first.
import { getNavHistory } from "navlens";
const history = getNavHistory();
// [{ path: '/products/42', timestamp: 1714000000000 }, ...]clearNavHistory(config?)
Clears all stored navigation history.
import { clearNavHistory } from "navlens";
clearNavHistory();pushEntry(path, config?)
Manually push a path into history. Used internally by all adapters.
import { pushEntry } from "navlens";
pushEntry("/custom-path");Config
All functions accept an optional config object:
interface NavHistoryConfig {
storageKey?: string; // default: 'navtrace_history'
maxAgeMs?: number; // default: 1800000 (30 min)
maxEntries?: number; // default: 50
storage?: "session" | "local"; // default: 'session'
}const config = {
storageKey: "my_app_nav",
maxAgeMs: 3600000, // 1 hour
maxEntries: 100,
storage: "local",
};
getPreviousPath(config);
getNavHistory(config);Design
core/has zero framework importssessionStorageby default,localStorageoptional- No duplicate consecutive entries stored
- Entries pruned by
maxAgeMs+ capped atmaxEntries - Storage errors caught silently (SSR-safe)
Contributing
Setup
git clone https://github.com/farzinfiroozi/navlens.git
cd navlens
pnpm installStructure
navlens/
├── packages/
│ └── navlens/ # npm package
│ ├── src/
│ │ ├── core/ # storage, helpers, types — zero framework deps
│ │ ├── adapters/ # one file per framework
│ │ ├── hooks/ # react/, vue/, svelte/
│ │ └── components/
│ └── tsup.config.ts
└── examples/
├── next/
├── react-router/
├── vue-router/
├── nuxt/
├── quasar/
└── sveltekit/Commands
# build the package
pnpm --filter navlens build
# watch mode
pnpm --filter navlens dev
# run tests
pnpm --filter navlens test
# typecheck
pnpm --filter navlens typecheckGuidelines
core/must stay framework-free- New adapter → add entry in
tsup.config.ts,package.jsonexports, andsrc/index.ts - All storage reads/writes must be wrapped in try/catch (SSR safety)
- No consecutive duplicate entries — enforced in
pushEntry
License
MIT © Farzin Firoozi
