@funstack/router 0.0.7 → 0.0.9

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.
@@ -26,8 +26,13 @@ yarn add @funstack/router`}</CodeBlock>
26
26
  <CodeBlock language="bash">{`npx funstack-router-skill-installer`}</CodeBlock>
27
27
  <p>
28
28
  The installer will guide you through setting up the skill for your
29
- preferred AI agent.
29
+ preferred AI agent. Alternatively, if you prefer{" "}
30
+ <a href="https://skills.sh/" target="_blank">
31
+ npx skills
32
+ </a>
33
+ , you can install it with:
30
34
  </p>
35
+ <CodeBlock language="bash">{`npx skills add uhyo/funstack-router`}</CodeBlock>
31
36
  </section>
32
37
 
33
38
  <section>
@@ -113,34 +118,6 @@ const routes = [
113
118
  component: UserProfile,
114
119
  }),
115
120
  ];`}</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
121
  </section>
145
122
 
146
123
  <section>
@@ -164,21 +141,21 @@ function UserProfilePage({
164
141
  data: Promise<User>;
165
142
  params: { userId: string };
166
143
  }) {
167
- const user = use(data);
168
144
  return (
169
145
  <Suspense fallback={<div>Loading...</div>}>
170
- <UserProfile user={user} params={params} />
146
+ <UserProfile data={data} params={params} />
171
147
  </Suspense>
172
148
  );
173
149
  }
174
150
 
175
151
  function UserProfile({
176
- user,
152
+ data,
177
153
  params,
178
154
  }: {
179
- user: User;
155
+ data: Promise<User>;
180
156
  params: { userId: string };
181
157
  }) {
158
+ const user = use(data);
182
159
  return (
183
160
  <div>
184
161
  <h1>{user.name}</h1>
@@ -155,15 +155,12 @@ function Navigation() {
155
155
  <p>
156
156
  While FUNSTACK Router handles navigation for you, you can interact
157
157
  directly with the Navigation API when needed. This is useful for
158
- features like scroll-to-top behavior or analytics tracking.
158
+ features like analytics tracking.
159
159
  </p>
160
160
  <CodeBlock language="tsx">{`import { useEffect } from "react";
161
161
 
162
162
  function App() {
163
163
  useEffect(() => {
164
- const navigation = window.navigation;
165
- if (!navigation) return;
166
-
167
164
  const controller = new AbortController();
168
165
 
169
166
  // Listen for successful navigation completion
@@ -338,7 +338,7 @@ const routes = [
338
338
  <CodeBlock language="tsx">{`import { use, Suspense } from "react";
339
339
  import { route, Outlet, useRouteData } from "@funstack/router";
340
340
 
341
- // Define the parent route with a loader
341
+ // Define the parent route with a loader and child routes
342
342
  const teamRoute = route({
343
343
  id: "team",
344
344
  path: "/teams/:teamId",
@@ -347,9 +347,14 @@ const teamRoute = route({
347
347
  const response = await fetch(\`/api/teams/\${params.teamId}\`);
348
348
  return response.json();
349
349
  },
350
+ children: [
351
+ route({ path: "/", component: TeamOverview }),
352
+ route({ path: "/members", component: TeamMembers }),
353
+ route({ path: "/settings", component: TeamSettings }),
354
+ ],
350
355
  });
351
356
 
352
- // Parent layout loads team data once
357
+ // Parent layout loads team data once, child routes render in <Outlet />
353
358
  function TeamLayoutContent({
354
359
  data,
355
360
  }: {
@@ -439,10 +444,7 @@ function TeamLayout(props: {
439
444
  Pathless routes also play a key role in server-side rendering. During
440
445
  SSR, only pathless routes render (since no URL is available on the
441
446
  server), making them ideal for defining the app shell. See the{" "}
442
- <a href="/funstack-router/learn/server-side-rendering">
443
- Server-Side Rendering
444
- </a>{" "}
445
- page for details.
447
+ <a href="/learn/ssr">Server-Side Rendering</a> page for details.
446
448
  </p>
447
449
  </section>
448
450
 
@@ -27,8 +27,7 @@ export function LearnRscPage() {
27
27
  <code>"use client"</code> because it exports components and hooks that
28
28
  depend on browser APIs (the Navigation API, React context, etc.). This
29
29
  means importing from <code>@funstack/router</code> in a server module
30
- would pull the entire router into the client bundle &mdash; defeating
31
- the purpose of RSC.
30
+ would fail.
32
31
  </p>
33
32
  <p>
34
33
  To solve this, the package provides a separate entry point:{" "}
@@ -68,9 +67,8 @@ export default function App() {
68
67
  In this example, <code>App</code> is a server component. It builds the
69
68
  route array using <code>route()</code> from{" "}
70
69
  <code>@funstack/router/server</code> and renders the{" "}
71
- <code>Router</code> component from <code>@funstack/router</code>.
72
- Since <code>Router</code> is a client component, the RSC bundler
73
- handles the client boundary automatically.
70
+ <code>Router</code> component from <code>@funstack/router</code> which
71
+ is a client component.
74
72
  </p>
75
73
  <h4>What the server entry point exports</h4>
76
74
  <ul>
@@ -90,45 +88,29 @@ export default function App() {
90
88
  </ul>
91
89
  </section>
92
90
 
93
- <section>
94
- <h3>The Client Boundary</h3>
95
- <p>
96
- The <code>Router</code> component subscribes to the Navigation API and
97
- manages React state, so it is a client component. It serves as the
98
- client boundary in your component tree &mdash; server components above
99
- it construct the route definitions, while <code>Router</code> and its
100
- runtime dependencies run in the browser.
101
- </p>
102
- <p>
103
- Route definitions (paths, component references, children) are plain
104
- serializable data, so they can be passed from a server component into{" "}
105
- <code>Router</code> as props.
106
- </p>
107
- </section>
108
-
109
91
  <section>
110
92
  <h3>Defining Routes in the Server Context</h3>
111
93
  <p>
112
- Because route definitions are plain data (paths, component references,
113
- and children), they can be constructed entirely on the server. The{" "}
114
- <code>route()</code> helper from <code>@funstack/router/server</code>{" "}
115
- produces the same <code>RouteDefinition</code> objects as the one from
116
- the main entry point &mdash; the only difference is that it does not
117
- pull in client-side code.
94
+ Route definitions can be defined in server modules because they are{" "}
95
+ <strong>plain data structures</strong> except for page components and
96
+ loader functions. Fortunately, it is possible to import both of these
97
+ from client modules which results in client references that can be
98
+ passed from the server to the client through the <code>routes</code>{" "}
99
+ prop.
118
100
  </p>
119
101
  <CodeBlock language="tsx">{`// App.tsx — Server Component
120
102
  import { Router } from "@funstack/router";
121
103
  import { route } from "@funstack/router/server";
122
104
  import { lazy } from "react";
123
105
 
124
- // Lazy-load page components these will be code-split
125
- const HomePage = lazy(() => import("./pages/HomePage.js"));
126
- const DashboardPage = lazy(() => import("./pages/DashboardPage.js"));
127
- const SettingsPage = lazy(() => import("./pages/SettingsPage.js"));
106
+ // Import page components from client modules
107
+ import HomePage from "./pages/HomePage.js";
108
+ import DashboardPage from "./pages/DashboardPage.js";
109
+ import SettingsPage from "./pages/SettingsPage.js";
128
110
 
129
111
  const routes = [
130
112
  route({
131
- component: <Layout />,
113
+ component: Layout,
132
114
  children: [
133
115
  route({ path: "/", component: HomePage }),
134
116
  route({ path: "/dashboard", component: DashboardPage }),
@@ -140,13 +122,6 @@ const routes = [
140
122
  export default function App() {
141
123
  return <Router routes={routes} />;
142
124
  }`}</CodeBlock>
143
- <p>
144
- Note that page components referenced in route definitions can be
145
- either server components or client components. When a page component
146
- uses hooks or browser APIs, it should have the{" "}
147
- <code>"use client"</code> directive. Otherwise, it can remain a server
148
- component for optimal performance.
149
- </p>
150
125
 
151
126
  <h4>Loaders in an RSC Context</h4>
152
127
  <p>
@@ -169,7 +144,7 @@ import { dashboardLoader } from "./loaders/dashboard.js";
169
144
 
170
145
  const routes = [
171
146
  route({
172
- component: <Layout />,
147
+ component: Layout,
173
148
  children: [
174
149
  route({ path: "/", component: HomePage }),
175
150
  route({
@@ -193,64 +168,77 @@ export default function App() {
193
168
  </section>
194
169
 
195
170
  <section>
196
- <h3>A Complete Example</h3>
171
+ <h3>Using Server Components as Route Components</h3>
197
172
  <p>
198
- This documentation site itself uses this pattern. Here is a simplified
199
- version of how it is structured:
173
+ All examples so far have used client components as route components,
174
+ but you can go even further and{" "}
175
+ <strong>use server components as route components</strong>. Actually,
176
+ this is the primary use case for the RSC support in FUNSTACK Router
177
+ &mdash; it allows pre-rendering each route on the server (or at build
178
+ time for static sites).
200
179
  </p>
201
- <CodeBlock language="tsx">{`// vite.config.ts
202
- import funstackStatic from "@funstack/static";
203
- import react from "@vitejs/plugin-react";
204
- import { defineConfig } from "vite";
205
180
 
206
- export default defineConfig({
207
- plugins: [
208
- funstackStatic({
209
- root: "./src/Root.tsx", // Server Component HTML shell
210
- app: "./src/App.tsx", // Server Component route definitions
211
- ssr: true,
212
- }),
213
- react(),
214
- ],
215
- });`}</CodeBlock>
216
- <CodeBlock language="tsx">{`// Root.tsx — Server Component (HTML shell)
217
- import type { ReactNode } from "react";
218
-
219
- export default function Root({ children }: { children: ReactNode }) {
220
- return (
221
- <html lang="en">
222
- <head>
223
- <meta charSet="UTF-8" />
224
- <title>My App</title>
225
- </head>
226
- <body>{children}</body>
227
- </html>
228
- );
229
- }`}</CodeBlock>
230
- <CodeBlock language="tsx">{`// App.tsx — Server Component (route definitions)
181
+ <h4>Use React Node as Route Components</h4>
182
+ <p>
183
+ When you use server components as route components, the route's{" "}
184
+ <code>component</code> must be a React node (i.e.{" "}
185
+ <code>&lt;MyComponent /&gt;</code>) instead of a component reference
186
+ (i.e. <code>MyComponent</code>) because a references to server
187
+ components cannot be passed to the client.
188
+ </p>
189
+ <CodeBlock language="tsx">{`// App.tsx — Server Component
231
190
  import { Router } from "@funstack/router";
232
191
  import { route } from "@funstack/router/server";
233
- import { Layout } from "./components/Layout.js";
234
- import { HomePage } from "./pages/HomePage.js";
235
- import { AboutPage } from "./pages/AboutPage.js";
192
+ import HomePage from "./pages/HomePage.js";
193
+ import AboutPage from "./pages/AboutPage.js";
236
194
 
237
195
  const routes = [
238
196
  route({
239
197
  component: <Layout />,
240
198
  children: [
241
- route({ path: "/", component: HomePage }),
242
- route({ path: "/about", component: AboutPage }),
199
+ route({ path: "/", component: <HomePage /> }),
200
+ route({ path: "/about", component: <AboutPage /> }),
243
201
  ],
244
202
  }),
245
203
  ];
246
204
 
247
205
  export default function App() {
248
- return <Router routes={routes} fallback="static" />;
206
+ return <Router routes={routes} />;
249
207
  }`}</CodeBlock>
250
208
  <p>
251
- In this setup, <code>Root</code> and <code>App</code> are server
252
- components. The route definitions are constructed on the server and
253
- passed into <code>Router</code>, which acts as the client boundary.
209
+ In this example, <code>HomePage</code> and <code>AboutPage</code> are
210
+ server components. They are rendered on the server and the resulting
211
+ HTML is sent to the client.
212
+ </p>
213
+ <p>
214
+ Due to this nature, a route component defined as a server component{" "}
215
+ <em>cannot</em> receive route props (params, search params, navigation
216
+ state, etc). We are exploring ways to lift this limitation in the
217
+ future, but for now if you need to access route props you will need to
218
+ use client components as route components.
219
+ </p>
220
+ <p>
221
+ For some use cases it is enough to have a client component child as a
222
+ pathless route:
223
+ </p>
224
+ <CodeBlock language="tsx">{`const routes = [
225
+ route({
226
+ path: "/",
227
+ component: <HomePage />, // Server Component
228
+ children: [
229
+ route({
230
+ component: InteractivePartOfHomePage, // Client Component
231
+ loader: someLoaderForHomePage,
232
+ }),
233
+ ],
234
+ }),
235
+ ];`}</CodeBlock>
236
+ <p>
237
+ In this example, <code>HomePage</code> is a server component that
238
+ renders the static parts of the page while{" "}
239
+ <code>InteractivePartOfHomePage</code> is a client component that can
240
+ access route props (like loader data). <code>HomePage</code> can
241
+ render <code>&lt;Outlet /&gt;</code> to render its child routes.
254
242
  </p>
255
243
  </section>
256
244
 
@@ -259,32 +247,27 @@ export default function App() {
259
247
  <ul>
260
248
  <li>
261
249
  Import <code>route</code> and <code>routeState</code> from{" "}
262
- <code>@funstack/router/server</code> in server modules to avoid
263
- pulling client code into the server module graph
250
+ <code>@funstack/router/server</code> to define routes in server
251
+ modules
264
252
  </li>
265
253
  <li>
266
254
  <code>Router</code> is a client component and serves as the client
267
255
  boundary &mdash; render it directly from your server component
268
256
  </li>
269
- <li>
270
- Route definitions are plain data and can be constructed entirely on
271
- the server
272
- </li>
273
257
  <li>
274
258
  Loaders run client-side &mdash; define them in{" "}
275
259
  <code>"use client"</code> modules and import them into your route
276
260
  definitions
277
261
  </li>
278
262
  <li>
279
- Page components can be either server components or client components
280
- depending on whether they need browser APIs or hooks
263
+ Page components can be either server components or client
264
+ components; if using server components, define them as React nodes
265
+ (e.g. <code>&lt;MyPage /&gt;</code>) instead of component references
266
+ (e.g. <code>MyPage</code>)
281
267
  </li>
282
268
  <li>
283
- See also the{" "}
284
- <a href="/funstack-router/learn/server-side-rendering">
285
- Server-Side Rendering
286
- </a>{" "}
287
- guide for how the router handles SSR and hydration
269
+ See also the <a href="/learn/ssr">Server-Side Rendering</a> guide
270
+ for how the router handles SSR and hydration
288
271
  </li>
289
272
  </ul>
290
273
  </section>
@@ -0,0 +1,133 @@
1
+ import { CodeBlock } from "../components/CodeBlock.js";
2
+
3
+ export function LearnSsgPage() {
4
+ return (
5
+ <div className="learn-content">
6
+ <h2>Static Site Generation</h2>
7
+
8
+ <p className="page-intro">
9
+ When your server or static site generator knows the URL being rendered,
10
+ you can use the <code>ssr</code> prop to match path-based routes during
11
+ SSR. This produces richer server-rendered HTML &mdash; users see page
12
+ content immediately instead of just the app shell.
13
+ </p>
14
+
15
+ <section>
16
+ <h3>
17
+ How the <code>ssr</code> Prop Works
18
+ </h3>
19
+ <p>
20
+ As described in the <a href="/learn/ssr">How SSR Works</a> guide, the
21
+ router normally has no URL during SSR and only renders pathless
22
+ routes. The <code>ssr</code> prop provides a pathname so path-based
23
+ routes can also match during SSR:
24
+ </p>
25
+ <CodeBlock language="tsx">{`// Server knows the requested URL and passes it to the router
26
+ <Router routes={routes} ssr={{ path: "/about" }} />`}</CodeBlock>
27
+ <p>
28
+ When <code>ssr</code> is provided, the router matches path-based
29
+ routes against <code>ssr.path</code> just as it would match against
30
+ the real URL on the client. Route params are extracted normally.
31
+ Routes with loaders are skipped by default &mdash; the parent route
32
+ renders as a shell, and loader content fills in after hydration.
33
+ </p>
34
+ <p>
35
+ Once the client hydrates, the real URL from the Navigation API takes
36
+ over and <code>ssr</code> is ignored.
37
+ </p>
38
+ </section>
39
+
40
+ <section>
41
+ <h3>Example</h3>
42
+ <p>
43
+ Consider a route tree with a mix of static pages and loader-based
44
+ routes:
45
+ </p>
46
+ <CodeBlock language="tsx">{`const routes = [
47
+ route({
48
+ component: AppShell,
49
+ children: [
50
+ route({ path: "/", component: HomePage }), // Matches ssr.path="/"
51
+ route({ path: "/about", component: AboutPage }),// Matches ssr.path="/about"
52
+ route({
53
+ path: "/dashboard",
54
+ component: DashboardPage,
55
+ loader: dashboardLoader, // Skipped during SSR (has loader)
56
+ }),
57
+ ],
58
+ }),
59
+ ];
60
+
61
+ // With ssr={{ path: "/about" }}:
62
+ // - AppShell renders (pathless, no loader) ✓
63
+ // - AboutPage renders (path matches, no loader) ✓
64
+ // - DashboardPage would NOT render with ssr={{ path: "/dashboard" }}
65
+ // because it has a loader`}</CodeBlock>
66
+ </section>
67
+
68
+ <section>
69
+ <h3>
70
+ When to Use <code>ssr</code>
71
+ </h3>
72
+ <p>
73
+ Use the <code>ssr</code> prop when your server or static site
74
+ generator knows the URL being rendered and you want to include
75
+ page-specific content in the SSR output. This is common for static
76
+ site generation, but can also be used in dynamic SSR scenarios where
77
+ the server can determine the URL at request time.
78
+ </p>
79
+ <p>This is particularly useful for:</p>
80
+ <ul>
81
+ <li>
82
+ Improving perceived performance by showing page content immediately
83
+ instead of a blank shell
84
+ </li>
85
+ <li>
86
+ SEO &mdash; search engine crawlers see the full page content rather
87
+ than just the app shell
88
+ </li>
89
+ <li>
90
+ Static site generation where each page is pre-rendered at a known
91
+ path
92
+ </li>
93
+ </ul>
94
+ </section>
95
+
96
+ <section>
97
+ <h3>Routes with Loaders</h3>
98
+ <p>
99
+ Routes with loaders are skipped by default during SSR. If your
100
+ application has a server runtime that can execute loaders at request
101
+ time, see the <a href="/learn/ssr/with-loaders">SSR with Loaders</a>{" "}
102
+ guide.
103
+ </p>
104
+ <p>
105
+ If you only need loaders to run at build time (not on the client),
106
+ consider using{" "}
107
+ <a href="/learn/react-server-components">React Server Components</a>{" "}
108
+ with SSG. RSC lets you fetch data on the server during the build and
109
+ send the result as static HTML, without shipping loader code to the
110
+ client.
111
+ </p>
112
+ </section>
113
+
114
+ <section>
115
+ <h3>Key Takeaways</h3>
116
+ <ul>
117
+ <li>
118
+ Use <code>ssr</code> to enable path-based route matching during SSR
119
+ for richer server-rendered output
120
+ </li>
121
+ <li>
122
+ Routes with loaders are skipped during SSR by default; the parent
123
+ route renders as a shell and loader content fills in after hydration
124
+ </li>
125
+ <li>
126
+ After hydration, the real URL from the Navigation API takes over and{" "}
127
+ <code>ssr</code> is ignored
128
+ </li>
129
+ </ul>
130
+ </section>
131
+ </div>
132
+ );
133
+ }
@@ -1,9 +1,9 @@
1
1
  import { CodeBlock } from "../components/CodeBlock.js";
2
2
 
3
- export function LearnSsrPage() {
3
+ export function LearnSsrBasicPage() {
4
4
  return (
5
5
  <div className="learn-content">
6
- <h2>Server-Side Rendering</h2>
6
+ <h2>How SSR Works</h2>
7
7
 
8
8
  <p className="page-intro">
9
9
  FUNSTACK Router supports server-side rendering with a two-stage model.
@@ -13,7 +13,7 @@ export function LearnSsrPage() {
13
13
  </p>
14
14
 
15
15
  <section>
16
- <h3>How SSR Works</h3>
16
+ <h3>Two-Stage Rendering</h3>
17
17
  <p>
18
18
  FUNSTACK Router uses a two-stage rendering model that separates what
19
19
  renders on the server from what renders on the client:
@@ -21,10 +21,9 @@ export function LearnSsrPage() {
21
21
  <p>
22
22
  <strong>Stage 1 &mdash; Server:</strong> No URL is available on the
23
23
  server. The router matches only pathless routes (routes without a{" "}
24
- <code>path</code> property) that do not have a loader. Pathless routes
25
- with loaders are skipped because there is no request context to run
26
- them. This produces the app shell &mdash; layouts, headers, navigation
27
- chrome, and other structural markup.
24
+ <code>path</code> property) that do not have a loader. This produces
25
+ the app shell &mdash; layouts, headers, navigation chrome, and other
26
+ structural markup.
28
27
  </p>
29
28
  <p>
30
29
  <strong>Stage 2 &mdash; Client hydration:</strong> Once the browser
@@ -34,13 +33,12 @@ export function LearnSsrPage() {
34
33
  </p>
35
34
  <CodeBlock language="tsx">{`// What renders at each stage:
36
35
 
37
- // Stage 1 (Server) Stage 2 (Client)
38
- // ───────────────── ─────────────────
39
- // App shell (pathless App shell (pathless)
40
- // without loader)
41
- // ✗ No path routes Path routes match
42
- // ✗ No loaders Loaders execute
43
- // ✗ No URL available ✓ URL from Navigation API`}</CodeBlock>
36
+ // Stage 1 (Server) Stage 2 (Client)
37
+ // ─────────────────────────── ─────────────────
38
+ // App shell (pathless routes) App shell (pathless)
39
+ // Path routes match
40
+ // ✗ No loaders Loaders execute
41
+ // ✗ No URL available URL from Navigation API`}</CodeBlock>
44
42
  </section>
45
43
 
46
44
  <section>
@@ -149,15 +147,40 @@ function HomePage() {
149
147
  </section>
150
148
 
151
149
  <section>
152
- <h3>Key Takeaways</h3>
150
+ <h3>Going Beyond the App Shell</h3>
151
+ <p>
152
+ The default SSR behavior produces only the app shell. This is perfect
153
+ for ordinary SPAs where only one HTML page is served and the client
154
+ takes over all routing. SSR can still be useful in this scenario,
155
+ normally with a static site generator, to improve perceived
156
+ performance by showing the shell immediately while the rest of the
157
+ page loads.
158
+ </p>
159
+ <p>
160
+ If your server or build tool knows the URL being rendered, you can use
161
+ the <code>ssr</code> prop to match path-based routes during SSR for
162
+ richer output:
163
+ </p>
153
164
  <ul>
154
165
  <li>
155
- During SSR, only pathless routes without loaders render (no URL or
156
- request context is available on the server)
166
+ <a href="/learn/ssr/static-site-generation">
167
+ Static Site Generation
168
+ </a>{" "}
169
+ &mdash; pre-render pages at known paths without running loaders
170
+ </li>
171
+ <li>
172
+ <a href="/learn/ssr/with-loaders">SSR with Loaders</a> &mdash;
173
+ render pages with loader data on the server for fully dynamic SSR
157
174
  </li>
175
+ </ul>
176
+ </section>
177
+
178
+ <section>
179
+ <h3>Key Takeaways</h3>
180
+ <ul>
158
181
  <li>
159
- Path-based routes, loaders, and pathless routes with loaders
160
- activate after client hydration
182
+ Only pathless routes without loaders render during SSR (no URL is
183
+ available on the server)
161
184
  </li>
162
185
  <li>
163
186
  Pathless routes are ideal for app shell markup (headers, footers,
@@ -170,8 +193,8 @@ function HomePage() {
170
193
  app shell
171
194
  </li>
172
195
  <li>
173
- This two-stage model keeps SSR output lightweight while enabling
174
- full interactivity on the client
196
+ Once the client hydrates, the real URL from the Navigation API takes
197
+ over
175
198
  </li>
176
199
  </ul>
177
200
  </section>