@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.
- package/dist/docs/ApiComponentsPage.tsx +33 -0
- package/dist/docs/ApiHooksPage.tsx +8 -3
- package/dist/docs/ApiTypesPage.tsx +56 -8
- package/dist/docs/ApiUtilitiesPage.tsx +26 -8
- package/dist/docs/ExamplesPage.tsx +445 -0
- package/dist/docs/GettingStartedPage.tsx +10 -33
- package/dist/docs/LearnNavigationApiPage.tsx +1 -4
- package/dist/docs/LearnNestedRoutesPage.tsx +8 -6
- package/dist/docs/LearnRscPage.tsx +77 -94
- package/dist/docs/LearnSsgPage.tsx +133 -0
- package/dist/docs/{LearnSsrPage.tsx → LearnSsrBasicPage.tsx} +44 -21
- package/dist/docs/LearnSsrWithLoadersPage.tsx +141 -0
- package/dist/docs/LearnTransitionsPage.tsx +80 -6
- package/dist/docs/LearnTypeSafetyPage.tsx +28 -22
- package/dist/docs/index.md +5 -2
- package/dist/index.d.mts +56 -25
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +118 -26
- package/dist/index.mjs.map +1 -1
- package/dist/{route-ClVnhrQD.d.mts → route-DRcgs0Pt.d.mts} +61 -6
- package/dist/route-DRcgs0Pt.d.mts.map +1 -0
- package/dist/route-p_gr5yPI.mjs.map +1 -1
- package/dist/server.d.mts +1 -1
- package/package.json +1 -1
- package/dist/route-ClVnhrQD.d.mts.map +0 -1
|
@@ -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
|
|
146
|
+
<UserProfile data={data} params={params} />
|
|
171
147
|
</Suspense>
|
|
172
148
|
);
|
|
173
149
|
}
|
|
174
150
|
|
|
175
151
|
function UserProfile({
|
|
176
|
-
|
|
152
|
+
data,
|
|
177
153
|
params,
|
|
178
154
|
}: {
|
|
179
|
-
|
|
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
|
|
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="/
|
|
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
|
|
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
|
-
|
|
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 — 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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
//
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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:
|
|
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:
|
|
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>
|
|
171
|
+
<h3>Using Server Components as Route Components</h3>
|
|
197
172
|
<p>
|
|
198
|
-
|
|
199
|
-
|
|
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
|
+
— 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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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><MyComponent /></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
|
|
234
|
-
import
|
|
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}
|
|
206
|
+
return <Router routes={routes} />;
|
|
249
207
|
}`}</CodeBlock>
|
|
250
208
|
<p>
|
|
251
|
-
In this
|
|
252
|
-
components.
|
|
253
|
-
|
|
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><Outlet /></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>
|
|
263
|
-
|
|
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 — 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 — 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
|
|
280
|
-
|
|
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><MyPage /></code>) instead of component references
|
|
266
|
+
(e.g. <code>MyPage</code>)
|
|
281
267
|
</li>
|
|
282
268
|
<li>
|
|
283
|
-
See also the
|
|
284
|
-
|
|
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 — 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 — 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 — 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
|
|
3
|
+
export function LearnSsrBasicPage() {
|
|
4
4
|
return (
|
|
5
5
|
<div className="learn-content">
|
|
6
|
-
<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>
|
|
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 — 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.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
chrome, and other structural markup.
|
|
24
|
+
<code>path</code> property) that do not have a loader. This produces
|
|
25
|
+
the app shell — layouts, headers, navigation chrome, and other
|
|
26
|
+
structural markup.
|
|
28
27
|
</p>
|
|
29
28
|
<p>
|
|
30
29
|
<strong>Stage 2 — 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)
|
|
38
|
-
//
|
|
39
|
-
// App shell (pathless
|
|
40
|
-
//
|
|
41
|
-
// ✗ No
|
|
42
|
-
// ✗ No
|
|
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>
|
|
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
|
-
|
|
156
|
-
|
|
166
|
+
<a href="/learn/ssr/static-site-generation">
|
|
167
|
+
Static Site Generation
|
|
168
|
+
</a>{" "}
|
|
169
|
+
— pre-render pages at known paths without running loaders
|
|
170
|
+
</li>
|
|
171
|
+
<li>
|
|
172
|
+
<a href="/learn/ssr/with-loaders">SSR with Loaders</a> —
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
174
|
-
|
|
196
|
+
Once the client hydrates, the real URL from the Navigation API takes
|
|
197
|
+
over
|
|
175
198
|
</li>
|
|
176
199
|
</ul>
|
|
177
200
|
</section>
|