tanstack-router-cache 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,151 @@
1
+ # Getting Started
2
+
3
+ `tanstack-router-cache` caches selected TanStack Router route views by keeping their rendered route tree mounted while it is hidden. Cached routes can preserve local React state, DOM state, scroll-sensitive UI, expensive list state, and in-progress forms without moving that state into a global store.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install tanstack-router-cache
9
+ ```
10
+
11
+ ```sh
12
+ bun add tanstack-router-cache
13
+ ```
14
+
15
+ ## Migration from TanStack Router
16
+
17
+ You do not need to change your router instance, route tree, loaders, search params, links, or navigation calls. Replace the outlet for the route branch that should support caching, then opt routes into caching with `staticData.routeCache`.
18
+
19
+ ### 1. Replace the outlet
20
+
21
+ Before:
22
+
23
+ ```tsx
24
+ import { Outlet } from "@tanstack/react-router";
25
+
26
+ export function AppShell() {
27
+ return <Outlet />;
28
+ }
29
+ ```
30
+
31
+ After:
32
+
33
+ ```tsx
34
+ import { RouterCacheOutlet, RouterCacheProvider } from "tanstack-router-cache";
35
+
36
+ export function AppShell() {
37
+ return (
38
+ <RouterCacheProvider maxEntries={8} maxEntriesPerRouteId={2}>
39
+ <RouterCacheOutlet />
40
+ </RouterCacheProvider>
41
+ );
42
+ }
43
+ ```
44
+
45
+ If your app shell has navigation, sidebars, headers, or providers around `Outlet`, keep that structure and replace only the route outlet area:
46
+
47
+ ```tsx
48
+ import { RouterCacheOutlet, RouterCacheProvider } from "tanstack-router-cache";
49
+
50
+ export function AppShell() {
51
+ return (
52
+ <RouterCacheProvider maxEntries={8} maxEntriesPerRouteId={2}>
53
+ <AppNavigation />
54
+ <RouterCacheOutlet />
55
+ </RouterCacheProvider>
56
+ );
57
+ }
58
+ ```
59
+
60
+ ### 2. Opt routes into caching
61
+
62
+ Regular TanStack Router routes continue to work as before. Add `routeCache: true` only to routes whose mounted view should be retained after navigation.
63
+
64
+ ```tsx
65
+ export const Route = createFileRoute("/customers")({
66
+ staticData: {
67
+ routeCache: true,
68
+ },
69
+ component: CustomersPage,
70
+ });
71
+ ```
72
+
73
+ Routes without `routeCache: true` are rendered and unmounted by TanStack Router normally.
74
+
75
+ ### 3. Pause route work while hidden
76
+
77
+ Existing `useEffect` calls still work. Use `useRouteCacheEffect` when an effect should run only while the cached route is visible.
78
+
79
+ ```tsx
80
+ import { useRouteCacheEffect } from "tanstack-router-cache";
81
+
82
+ function CustomersPage() {
83
+ useRouteCacheEffect(() => {
84
+ const controller = new AbortController();
85
+
86
+ refreshCustomers({ signal: controller.signal });
87
+
88
+ return () => {
89
+ controller.abort();
90
+ };
91
+ }, []);
92
+
93
+ return <CustomersTable />;
94
+ }
95
+ ```
96
+
97
+ Use `useRouteCacheActive` when child components need a boolean active state instead of an effect.
98
+
99
+ ## Basic setup
100
+
101
+ Place `RouterCacheProvider` and `RouterCacheOutlet` where TanStack Router would normally render child routes.
102
+
103
+ ```tsx
104
+ import { RouterCacheOutlet, RouterCacheProvider } from "tanstack-router-cache";
105
+
106
+ export function AppShell() {
107
+ return (
108
+ <RouterCacheProvider maxEntries={8} maxEntriesPerRouteId={2}>
109
+ <RouterCacheOutlet />
110
+ </RouterCacheProvider>
111
+ );
112
+ }
113
+ ```
114
+
115
+ Mark routes that should be cached with `staticData.routeCache`.
116
+
117
+ ```tsx
118
+ export const Route = createFileRoute("/customers")({
119
+ staticData: {
120
+ routeCache: true,
121
+ },
122
+ component: CustomersPage,
123
+ });
124
+ ```
125
+
126
+ Routes without `routeCache: true` are rendered normally and are removed when TanStack Router unmounts them.
127
+
128
+ ## Route static data
129
+
130
+ The package augments TanStack Router's `StaticDataRouteOption` type:
131
+
132
+ ```ts
133
+ declare module "@tanstack/react-router" {
134
+ interface StaticDataRouteOption {
135
+ routeCache?: boolean;
136
+ }
137
+ }
138
+ ```
139
+
140
+ Set `routeCache: true` on the route whose rendered view should be retained.
141
+
142
+ ```tsx
143
+ export const Route = createFileRoute("/reports")({
144
+ staticData: {
145
+ routeCache: true,
146
+ },
147
+ component: ReportsPage,
148
+ });
149
+ ```
150
+
151
+ If multiple child matches are present, the cache manager checks child route static data from deepest to shallowest and uses the deepest retained route data.
package/docs/hooks.md ADDED
@@ -0,0 +1,187 @@
1
+ # Hooks
2
+
3
+ ## `useRouterCache`
4
+
5
+ Controls cached route entries.
6
+
7
+ ```tsx
8
+ import { useRouterCache } from "tanstack-router-cache";
9
+
10
+ function CacheTools() {
11
+ const { cachedRoutes, destroy, destroyAll, invalidateWhere, isCached } =
12
+ useRouterCache();
13
+
14
+ return (
15
+ <button type="button" onClick={() => destroy("/customers")}>
16
+ Clear customers
17
+ </button>
18
+ );
19
+ }
20
+ ```
21
+
22
+ Returns:
23
+
24
+ | Field | Type | Description |
25
+ | --- | --- | --- |
26
+ | `cachedRoutes` | `CachedRoutes` | Current cached route data keyed by normalized pathname. |
27
+ | `destroy` | `(pathname: string | string[]) => void` | Removes one or more cached pathnames. |
28
+ | `destroyAll` | `() => void` | Removes every cached route entry. |
29
+ | `invalidateWhere` | `(predicate: (pathname: string, route: CachedRouteData) => boolean) => string[]` | Removes entries that match a predicate and returns the removed pathnames. |
30
+ | `isCached` | `(pathname: string) => boolean` | Returns whether a normalized pathname currently exists in the cache. |
31
+
32
+ Example: remove all cached entries for one route id.
33
+
34
+ ```tsx
35
+ const { invalidateWhere } = useRouterCache();
36
+
37
+ invalidateWhere((_, route) => route.routeId === "/customers/$customerId");
38
+ ```
39
+
40
+ ## `useRouteCacheActive`
41
+
42
+ Returns whether a cached route is currently visible.
43
+
44
+ ```tsx
45
+ import { useRouteCacheActive } from "tanstack-router-cache";
46
+
47
+ function CustomersPage() {
48
+ const isActive = useRouteCacheActive();
49
+
50
+ return <CustomersTable paused={!isActive} />;
51
+ }
52
+ ```
53
+
54
+ Signature:
55
+
56
+ ```ts
57
+ function useRouteCacheActive(pathname?: string): boolean;
58
+ ```
59
+
60
+ If `pathname` is omitted, the hook uses the current route pathname. Pass a pathname when a parent component needs to observe another cached route.
61
+
62
+ ## `useRouteCacheEffect`
63
+
64
+ Runs an effect only while the current cached route is visible. Cleanup runs when the route becomes hidden, when dependencies change, and when the component unmounts.
65
+
66
+ ```tsx
67
+ import { useRouteCacheEffect } from "tanstack-router-cache";
68
+
69
+ function CustomersPage() {
70
+ useRouteCacheEffect(() => {
71
+ const controller = new AbortController();
72
+
73
+ refreshCustomers({ signal: controller.signal });
74
+
75
+ return () => {
76
+ controller.abort();
77
+ };
78
+ }, []);
79
+
80
+ return <CustomersTable />;
81
+ }
82
+ ```
83
+
84
+ Signature:
85
+
86
+ ```ts
87
+ function useRouteCacheEffect(
88
+ activeCallback: React.EffectCallback,
89
+ deps?: React.DependencyList
90
+ ): void;
91
+ ```
92
+
93
+ Use this for polling, subscriptions, observers, expensive timers, or async work that should pause while the route is cached but hidden.
94
+
95
+ ## `useRouteCacheActivity`
96
+
97
+ Subscribes to active/inactive changes for the current route.
98
+
99
+ ```tsx
100
+ import { useRouteCacheActivity } from "tanstack-router-cache";
101
+
102
+ function CustomersPage() {
103
+ useRouteCacheActivity((active) => {
104
+ if (active) {
105
+ console.log("Customers became visible");
106
+ }
107
+ });
108
+
109
+ return <CustomersTable />;
110
+ }
111
+ ```
112
+
113
+ Signature:
114
+
115
+ ```ts
116
+ function useRouteCacheActivity(fn: (active: boolean) => void): void;
117
+ ```
118
+
119
+ `active` is `true` when the route is visible and `false` when it is hidden or removed from the visible route position.
120
+
121
+ ## `useRouteCacheNavigation`
122
+
123
+ Reports navigation timing for cached-route restores.
124
+
125
+ ```tsx
126
+ import { useRouteCacheNavigation } from "tanstack-router-cache";
127
+
128
+ function NavigationProgress() {
129
+ const { activeNavigation, lastCompletedNavigation } =
130
+ useRouteCacheNavigation();
131
+
132
+ if (activeNavigation) {
133
+ return <Spinner />;
134
+ }
135
+
136
+ return lastCompletedNavigation ? (
137
+ <span>{Math.round(lastCompletedNavigation.duration)} ms</span>
138
+ ) : null;
139
+ }
140
+ ```
141
+
142
+ Returns:
143
+
144
+ | Field | Type | Description |
145
+ | --- | --- | --- |
146
+ | `activeNavigation` | `RouteCacheNavigationStart | null` | The current cached navigation, if one is in progress. |
147
+ | `lastCompletedNavigation` | `RouteCacheNavigationComplete | null` | Timing data for the most recent completed cached navigation. |
148
+
149
+ `RouteCacheNavigationStart`:
150
+
151
+ | Field | Type | Description |
152
+ | --- | --- | --- |
153
+ | `pathname` | `string` | Cached route pathname being restored. |
154
+ | `startedAt` | `number` | `performance.now()` timestamp when cached navigation began. |
155
+
156
+ `RouteCacheNavigationComplete`:
157
+
158
+ | Field | Type | Description |
159
+ | --- | --- | --- |
160
+ | `pathname` | `string` | Cached route pathname that completed. |
161
+ | `startedAt` | `number` | Start timestamp from the matching navigation. |
162
+ | `visibleAt` | `number` | Timestamp after the cached route became visible. |
163
+ | `paintedAt` | `number` | Timestamp after the next animation frame. |
164
+ | `duration` | `number` | `paintedAt - startedAt`. |
165
+
166
+ ## `useRouteCacheErrorBoundary`
167
+
168
+ Marks the current route as errored while an error boundary fallback is mounted. Errored routes are removed from the cache so users do not keep returning to a failed cached view.
169
+
170
+ ```tsx
171
+ import { useRouteCacheErrorBoundary } from "tanstack-router-cache";
172
+
173
+ function RouteErrorFallback() {
174
+ useRouteCacheErrorBoundary();
175
+
176
+ return <p>Something went wrong.</p>;
177
+ }
178
+ ```
179
+
180
+ Signature:
181
+
182
+ ```ts
183
+ function useRouteCacheErrorBoundary(pathname?: string): void;
184
+ ```
185
+
186
+ If `pathname` is omitted, the hook uses the current route pathname.
187
+
package/docs/types.md ADDED
@@ -0,0 +1,37 @@
1
+ # Types
2
+
3
+ ## Exported types
4
+
5
+ ```ts
6
+ export type ActivityMode = "visible" | "hidden";
7
+
8
+ export type RouteCacheNavigationStart = {
9
+ pathname: string;
10
+ startedAt: number;
11
+ };
12
+
13
+ export type RouteCacheNavigationComplete = RouteCacheNavigationStart & {
14
+ duration: number;
15
+ paintedAt: number;
16
+ visibleAt: number;
17
+ };
18
+ ```
19
+
20
+ The package also uses these cache shapes in public return values:
21
+
22
+ ```ts
23
+ type CachedRouteData = {
24
+ createdAt?: number;
25
+ href?: string;
26
+ lastVisibleAt?: number;
27
+ routeId?: string;
28
+ staticData: StaticDataRouteOption;
29
+ matchId?: string;
30
+ routerSnapshot?: RouterSnapshot;
31
+ ready?: boolean;
32
+ };
33
+
34
+ type CachedRoutes = {
35
+ [key: string]: CachedRouteData;
36
+ };
37
+ ```
package/docs/usage.md ADDED
@@ -0,0 +1,11 @@
1
+ # Documentation
2
+
3
+ Use these pages for the package API and behavior details.
4
+
5
+ - [Getting started](./getting-started.md): install, provider setup, and route flags.
6
+ - [Components](./components.md): `RouterCacheProvider` and `RouterCacheOutlet`.
7
+ - [Hooks](./hooks.md): cache controls, active-state hooks, lifecycle hooks, navigation timing, and error-boundary integration.
8
+ - [Cache behavior](./cache-behavior.md): eviction, pathname handling, hidden containers, and memory notes.
9
+ - [Debugging](./debugging.md): development debug API and snapshots.
10
+ - [Types](./types.md): exported types and cache data shapes.
11
+ - [Architecture](./architecture.md): provider state, router snapshots, rendering flow, events, and memory model.
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "tanstack-router-cache",
3
+ "version": "0.1.0",
4
+ "description": "Route view caching for TanStack Router.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Santiago",
8
+ "keywords": [
9
+ "react",
10
+ "tanstack-router",
11
+ "router",
12
+ "route-cache",
13
+ "route-state",
14
+ "view-cache"
15
+ ],
16
+ "files": [
17
+ "dist",
18
+ "docs",
19
+ "LICENSE",
20
+ "README.md"
21
+ ],
22
+ "exports": {
23
+ ".": {
24
+ "types": "./dist/index.d.ts",
25
+ "import": "./dist/index.js",
26
+ "require": "./dist/index.cjs"
27
+ }
28
+ },
29
+ "main": "./dist/index.cjs",
30
+ "module": "./dist/index.js",
31
+ "types": "./dist/index.d.ts",
32
+ "publishConfig": {
33
+ "access": "public",
34
+ "registry": "https://registry.npmjs.org/"
35
+ },
36
+ "scripts": {
37
+ "build": "bun run clean && rolldown -c && tsc",
38
+ "typecheck": "tsc --noEmit",
39
+ "clean": "node -e \"fs.rmSync('dist',{recursive:true,force:true})\"",
40
+ "lint": "biome check .",
41
+ "lint:fix": "biome check . --write",
42
+ "pack:dry-run": "bun pm pack --dry-run",
43
+ "prepack": "bun run build",
44
+ "prepublishOnly": "bun run lint && bun run typecheck"
45
+ },
46
+ "sideEffects": false,
47
+ "peerDependencies": {
48
+ "@tanstack/react-router": ">=1.168.14 <2.0.0",
49
+ "react": ">=19.0.0 <20.0.0"
50
+ },
51
+ "dependencies": {},
52
+ "devDependencies": {
53
+ "@biomejs/biome": "2.4.15",
54
+ "@tanstack/react-router": "1.170.11",
55
+ "@types/node": "25.9.1",
56
+ "@types/react": "19.2.16",
57
+ "@types/react-dom": "19.2.3",
58
+ "react": "19.2.7",
59
+ "react-dom": "19.2.7",
60
+ "rolldown": "1.1.0",
61
+ "typescript": "6.0.3"
62
+ }
63
+ }