@funstack/router 0.0.6 → 0.0.7

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,201 @@
1
+ import { CodeBlock } from "../components/CodeBlock.js";
2
+
3
+ export function GettingStartedPage() {
4
+ return (
5
+ <div className="page docs-page">
6
+ <h1>Getting Started</h1>
7
+
8
+ <section>
9
+ <h2>Installation</h2>
10
+ <p>Install the package using your preferred package manager:</p>
11
+ <CodeBlock language="bash">{`npm install @funstack/router
12
+ # or
13
+ pnpm add @funstack/router
14
+ # or
15
+ yarn add @funstack/router`}</CodeBlock>
16
+ </section>
17
+
18
+ <section>
19
+ <h2>AI Coding Agent Support</h2>
20
+ <p>
21
+ <code>@funstack/router</code> ships with an Agent skill that gives
22
+ your coding assistant (Claude Code, Cursor, GitHub Copilot, etc.)
23
+ knowledge about the router's API and best practices. After installing
24
+ the package, run:
25
+ </p>
26
+ <CodeBlock language="bash">{`npx funstack-router-skill-installer`}</CodeBlock>
27
+ <p>
28
+ The installer will guide you through setting up the skill for your
29
+ preferred AI agent.
30
+ </p>
31
+ </section>
32
+
33
+ <section>
34
+ <h2>Browser Support</h2>
35
+ <p>
36
+ FUNSTACK Router uses the{" "}
37
+ <a
38
+ href="https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API"
39
+ target="_blank"
40
+ rel="noopener noreferrer"
41
+ >
42
+ Navigation API
43
+ </a>{" "}
44
+ which is supported in Chrome 102+, Edge 102+, Firefox 147+, Safari
45
+ 26.2+.
46
+ </p>
47
+ </section>
48
+
49
+ <section>
50
+ <h2>Basic Setup</h2>
51
+ <p>
52
+ Create your routes using the <code>route</code> helper function and
53
+ render them with the <code>Router</code> component:
54
+ </p>
55
+ <CodeBlock language="tsx">{`import { Router, route, Outlet } from "@funstack/router";
56
+
57
+ // Define your page components
58
+ function Home() {
59
+ return <h1>Welcome Home</h1>;
60
+ }
61
+
62
+ function About() {
63
+ return <h1>About Us</h1>;
64
+ }
65
+
66
+ function Layout() {
67
+ return (
68
+ <div>
69
+ <nav>
70
+ <a href="/">Home</a>
71
+ <a href="/about">About</a>
72
+ </nav>
73
+ <Outlet />
74
+ </div>
75
+ );
76
+ }
77
+
78
+ // Define your routes
79
+ const routes = [
80
+ route({
81
+ path: "/",
82
+ component: Layout,
83
+ children: [
84
+ route({ path: "/", component: Home }),
85
+ route({ path: "/about", component: About }),
86
+ ],
87
+ }),
88
+ ];
89
+
90
+ // Render the router
91
+ function App() {
92
+ return <Router routes={routes} />;
93
+ }`}</CodeBlock>
94
+ </section>
95
+
96
+ <section>
97
+ <h2>Route Parameters</h2>
98
+ <p>
99
+ Define dynamic segments in your paths using the <code>:param</code>{" "}
100
+ syntax. Route components receive parameters via the{" "}
101
+ <code>params</code> prop, which is fully typed based on the path
102
+ pattern:
103
+ </p>
104
+ <CodeBlock language="tsx">{`import { route } from "@funstack/router";
105
+
106
+ function UserProfile({ params }: { params: { userId: string } }) {
107
+ return <h1>User: {params.userId}</h1>;
108
+ }
109
+
110
+ const routes = [
111
+ route({
112
+ path: "/users/:userId",
113
+ component: UserProfile,
114
+ }),
115
+ ];`}</CodeBlock>
116
+ <p>
117
+ Alternatively, you can use the <code>useParams</code> hook to access
118
+ parameters:
119
+ </p>
120
+ <CodeBlock language="tsx">{`import { useParams } from "@funstack/router";
121
+
122
+ function UserProfile() {
123
+ const params = useParams<{ userId: string }>();
124
+ return <h1>User: {params.userId}</h1>;
125
+ }`}</CodeBlock>
126
+ </section>
127
+
128
+ <section>
129
+ <h2>Programmatic Navigation</h2>
130
+ <p>
131
+ Use the <code>useNavigate</code> hook for programmatic navigation:
132
+ </p>
133
+ <CodeBlock language="tsx">{`import { useNavigate } from "@funstack/router";
134
+
135
+ function MyComponent() {
136
+ const navigate = useNavigate();
137
+
138
+ const handleClick = () => {
139
+ navigate("/about");
140
+ };
141
+
142
+ return <button onClick={handleClick}>Go to About</button>;
143
+ }`}</CodeBlock>
144
+ </section>
145
+
146
+ <section>
147
+ <h2>Data Loading</h2>
148
+ <p>
149
+ Use the <code>loader</code> option to fetch data before rendering a
150
+ route. The component receives both <code>data</code> (from the loader)
151
+ and <code>params</code> (from the URL) as props:
152
+ </p>
153
+ <CodeBlock language="tsx">{`import { route } from "@funstack/router";
154
+
155
+ interface User {
156
+ id: string;
157
+ name: string;
158
+ }
159
+
160
+ function UserProfilePage({
161
+ data,
162
+ params,
163
+ }: {
164
+ data: Promise<User>;
165
+ params: { userId: string };
166
+ }) {
167
+ const user = use(data);
168
+ return (
169
+ <Suspense fallback={<div>Loading...</div>}>
170
+ <UserProfile user={user} params={params} />
171
+ </Suspense>
172
+ );
173
+ }
174
+
175
+ function UserProfile({
176
+ user,
177
+ params,
178
+ }: {
179
+ user: User;
180
+ params: { userId: string };
181
+ }) {
182
+ return (
183
+ <div>
184
+ <h1>{user.name}</h1>
185
+ <p>User ID: {params.userId}</p>
186
+ </div>
187
+ );
188
+ }
189
+
190
+ const userRoute = route({
191
+ path: "/users/:userId",
192
+ component: UserProfilePage,
193
+ loader: async ({ params }): Promise<User> => {
194
+ const response = await fetch(\`/api/users/\${params.userId}\`);
195
+ return response.json();
196
+ },
197
+ });`}</CodeBlock>
198
+ </section>
199
+ </div>
200
+ );
201
+ }
@@ -0,0 +1,255 @@
1
+ import { CodeBlock } from "../components/CodeBlock.js";
2
+
3
+ export function LearnNavigationApiPage() {
4
+ return (
5
+ <div className="learn-content">
6
+ <h2>Navigation API</h2>
7
+
8
+ <p className="page-intro">
9
+ FUNSTACK Router is built on the{" "}
10
+ <a
11
+ href="https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API"
12
+ target="_blank"
13
+ rel="noopener noreferrer"
14
+ >
15
+ Navigation API
16
+ </a>
17
+ , a modern browser API that provides a unified way to handle navigation.
18
+ This guide explains the key differences from the older History API and
19
+ the benefits this brings to your application.
20
+ </p>
21
+
22
+ <section>
23
+ <h3>History API Limitations</h3>
24
+ <p>
25
+ The History API has been the foundation of single-page application
26
+ routing for years, but it comes with significant limitations that make
27
+ building routers more complex than necessary.
28
+ </p>
29
+ <p>
30
+ <strong>Reactive, not proactive:</strong> The <code>popstate</code>{" "}
31
+ event fires <em>after</em> navigation has already occurred. This makes
32
+ it difficult to intercept navigation, perform async operations like
33
+ data fetching, or cancel navigation based on conditions.
34
+ </p>
35
+ <CodeBlock language="typescript">{`// History API: popstate fires AFTER navigation
36
+ window.addEventListener("popstate", (event) => {
37
+ // Too late! The URL has already changed.
38
+ // We can only react to what happened.
39
+ console.log("Navigated to:", location.pathname);
40
+ });
41
+
42
+ // For programmatic navigation, we must manually call pushState
43
+ // AND then update the UI ourselves
44
+ history.pushState(null, "", "/new-page");
45
+ updateUI(); // Manually trigger UI update`}</CodeBlock>
46
+ <p>
47
+ <strong>Fragmented event model:</strong> Different navigation types
48
+ require different handling. Browser back/forward triggers{" "}
49
+ <code>popstate</code>, but <code>pushState</code> and{" "}
50
+ <code>replaceState</code> don't trigger any events. This leads to
51
+ scattered navigation logic.
52
+ </p>
53
+ <p>
54
+ <strong>Custom Link components required:</strong> Since clicking an{" "}
55
+ <code>{"<a>"}</code> tag causes a full page reload, routers must
56
+ provide custom <code>{"<Link>"}</code> components that intercept
57
+ clicks and call <code>pushState</code> instead.
58
+ </p>
59
+ </section>
60
+
61
+ <section>
62
+ <h3>The Navigation API Paradigm</h3>
63
+ <p>
64
+ The Navigation API fundamentally changes how navigation works by
65
+ providing a single <code>navigate</code> event that fires{" "}
66
+ <em>before</em> navigation commits. This allows routers to intercept
67
+ and handle navigation declaratively.
68
+ </p>
69
+ <CodeBlock language="typescript">{`// Navigation API: navigate event fires BEFORE navigation
70
+ navigation.addEventListener("navigate", (event) => {
71
+ // Navigation hasn't happened yet - we can intercept it!
72
+ const url = new URL(event.destination.url);
73
+
74
+ // Check the navigation type
75
+ console.log(event.navigationType); // "push", "replace", "reload", or "traverse"
76
+
77
+ // Intercept and handle with async operations
78
+ event.intercept({
79
+ async handler() {
80
+ // Fetch data, render components, etc.
81
+ const data = await fetchPageData(url.pathname);
82
+ renderPage(data);
83
+ // Navigation completes when handler resolves
84
+ }
85
+ });
86
+ });`}</CodeBlock>
87
+ <p>Key benefits of this approach:</p>
88
+ <ul>
89
+ <li>
90
+ <strong>Single unified event</strong> &mdash; All navigation types
91
+ (link clicks, back/forward, programmatic) trigger the same{" "}
92
+ <code>navigate</code> event
93
+ </li>
94
+ <li>
95
+ <strong>Interception with async support</strong> &mdash; The{" "}
96
+ <code>intercept()</code> method lets you run async handlers before
97
+ navigation completes
98
+ </li>
99
+ <li>
100
+ <strong>Built-in navigation type detection</strong> &mdash;{" "}
101
+ <code>event.navigationType</code> tells you exactly what kind of
102
+ navigation occurred
103
+ </li>
104
+ </ul>
105
+ </section>
106
+
107
+ <section>
108
+ <h3>
109
+ Native <code>{"<a>"}</code> Elements for SPA Links
110
+ </h3>
111
+ <p>
112
+ One of the most practical benefits of the Navigation API is that
113
+ standard <code>{"<a>"}</code> elements work for SPA navigation without
114
+ any special handling. The router intercepts navigation events from
115
+ native links automatically.
116
+ </p>
117
+ <CodeBlock language="tsx">{`// With FUNSTACK Router, native <a> elements just work!
118
+ function Navigation() {
119
+ return (
120
+ <nav>
121
+ {/* These are standard HTML anchor tags */}
122
+ <a href="/home">Home</a>
123
+ <a href="/about">About</a>
124
+ <a href="/users/123">User Profile</a>
125
+ </nav>
126
+ );
127
+ }
128
+
129
+ // No special <Link> component needed for basic navigation
130
+ // The router intercepts the navigate event and handles it as SPA navigation`}</CodeBlock>
131
+ <p>
132
+ This means you can use standard HTML in your components, and
133
+ navigation "just works". The router intercepts same-origin navigation
134
+ events and handles them without a full page reload.
135
+ </p>
136
+ <p>This approach has several advantages over custom Link components:</p>
137
+ <ul>
138
+ <li>
139
+ <strong>Standard HTML</strong> &mdash; No need to import and use
140
+ special components for every link
141
+ </li>
142
+ <li>
143
+ <strong>Progressive enhancement</strong> &mdash; Links work even if
144
+ JavaScript fails to load
145
+ </li>
146
+ <li>
147
+ <strong>Familiar patterns</strong> &mdash; Developers can use the{" "}
148
+ <code>{"<a>"}</code> tag they already know
149
+ </li>
150
+ </ul>
151
+ </section>
152
+
153
+ <section>
154
+ <h3>Accessing Navigation API Events</h3>
155
+ <p>
156
+ While FUNSTACK Router handles navigation for you, you can interact
157
+ directly with the Navigation API when needed. This is useful for
158
+ features like scroll-to-top behavior or analytics tracking.
159
+ </p>
160
+ <CodeBlock language="tsx">{`import { useEffect } from "react";
161
+
162
+ function App() {
163
+ useEffect(() => {
164
+ const navigation = window.navigation;
165
+ if (!navigation) return;
166
+
167
+ const controller = new AbortController();
168
+
169
+ // Listen for successful navigation completion
170
+ navigation.addEventListener(
171
+ "navigatesuccess",
172
+ () => {
173
+ // Track page view for analytics
174
+ analytics.trackPageView(location.pathname);
175
+ },
176
+ { signal: controller.signal }
177
+ );
178
+
179
+ return () => controller.abort();
180
+ }, []);
181
+
182
+ return <Router routes={routes} />;
183
+ }`}</CodeBlock>
184
+ <p>
185
+ The <code>navigatesuccess</code> event fires after navigation
186
+ completes successfully, making it ideal for post-navigation actions.
187
+ You can also use <code>navigation.transition</code> to track in-flight
188
+ navigations or implement loading indicators.
189
+ </p>
190
+ <p>
191
+ <strong>Note:</strong> be careful when adding event listeners for{" "}
192
+ <code>navigate</code> events since it may interfere with the router's
193
+ own handling. Consider using <code>onNavigate</code> prop on the{" "}
194
+ <code>{"<Router>"}</code> component for most use cases.
195
+ </p>
196
+ </section>
197
+
198
+ <section>
199
+ <h3>Other Navigation API Features</h3>
200
+ <p>
201
+ The Navigation API provides several advanced features that you can
202
+ leverage directly when needed:
203
+ </p>
204
+ <ul>
205
+ <li>
206
+ <strong>NavigationHistoryEntry</strong> &mdash; Each history entry
207
+ has a unique <code>id</code> and <code>key</code>, plus a{" "}
208
+ <code>dispose</code> event that fires when the entry is removed from
209
+ history
210
+ </li>
211
+ <li>
212
+ <strong>
213
+ Ephemeral <code>info</code>
214
+ </strong>{" "}
215
+ &mdash; Pass non-persisted context data during navigation via{" "}
216
+ <code>navigation.navigate(url, {"{ info }"}))</code>
217
+ </li>
218
+ <li>
219
+ <strong>navigation.entries()</strong> &mdash; Access the full
220
+ navigation history stack programmatically
221
+ </li>
222
+ </ul>
223
+ <p>
224
+ For more details, see the{" "}
225
+ <a
226
+ href="https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API"
227
+ target="_blank"
228
+ rel="noopener noreferrer"
229
+ >
230
+ MDN Navigation API documentation
231
+ </a>
232
+ .
233
+ </p>
234
+ </section>
235
+
236
+ <section>
237
+ <h3>Key Takeaways</h3>
238
+ <ul>
239
+ <li>
240
+ <strong>
241
+ Native <code>{"<a>"}</code> elements work
242
+ </strong>{" "}
243
+ &mdash; No special Link component required for SPA navigation; the
244
+ router intercepts standard anchor tags automatically
245
+ </li>
246
+ <li>
247
+ <strong>Direct API access when needed</strong> &mdash; Use events
248
+ like <code>navigatesuccess</code> for scroll restoration, analytics,
249
+ or other post-navigation actions
250
+ </li>
251
+ </ul>
252
+ </section>
253
+ </div>
254
+ );
255
+ }