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.
- package/LICENSE +22 -0
- package/README.md +84 -0
- package/dist/components/cached-outlet.d.ts +8 -0
- package/dist/components/off-screen-in.d.ts +9 -0
- package/dist/components/off-screen.d.ts +4 -0
- package/dist/components/restore-cached-href.d.ts +10 -0
- package/dist/components/route-cache-manager.d.ts +2 -0
- package/dist/components/router-cache-outlet.d.ts +4 -0
- package/dist/contexts/router-cache.d.ts +36 -0
- package/dist/dom/dismiss-transient-ui.d.ts +3 -0
- package/dist/hooks/use-event-listener.d.ts +36 -0
- package/dist/hooks/use-route-cache-active.d.ts +1 -0
- package/dist/hooks/use-route-cache-activity.d.ts +1 -0
- package/dist/hooks/use-route-cache-effect.d.ts +2 -0
- package/dist/hooks/use-route-cache-error-boundary.d.ts +1 -0
- package/dist/hooks/use-route-cache-navigation.d.ts +7 -0
- package/dist/hooks/use-router-cache-debug.d.ts +24 -0
- package/dist/hooks/use-router-cache.d.ts +10 -0
- package/dist/hooks/use-update.d.ts +1 -0
- package/dist/index.cjs +66 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +10 -0
- package/dist/pathname.d.ts +1 -0
- package/dist/types.d.ts +10 -0
- package/docs/architecture.md +503 -0
- package/docs/cache-behavior.md +28 -0
- package/docs/components.md +41 -0
- package/docs/debugging.md +31 -0
- package/docs/getting-started.md +151 -0
- package/docs/hooks.md +187 -0
- package/docs/types.md +37 -0
- package/docs/usage.md +11 -0
- package/package.json +63 -0
|
@@ -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
|
+
}
|