@funstack/static 0.0.2 → 0.0.4
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/README.md +12 -3
- package/dist/bin/skill-installer.d.mts +1 -0
- package/dist/bin/skill-installer.mjs +12 -0
- package/dist/bin/skill-installer.mjs.map +1 -0
- package/dist/build/buildApp.mjs +4 -3
- package/dist/build/buildApp.mjs.map +1 -1
- package/dist/build/rscProcessor.mjs +9 -3
- package/dist/build/rscProcessor.mjs.map +1 -1
- package/dist/client/entry.d.mts +1 -0
- package/dist/client/entry.mjs +17 -9
- package/dist/client/entry.mjs.map +1 -1
- package/dist/client/globals.mjs.map +1 -1
- package/dist/docs/FAQ.md +5 -0
- package/dist/docs/GettingStarted.md +180 -0
- package/dist/docs/MigratingFromViteSPA.md +321 -0
- package/dist/docs/api/Defer.md +110 -0
- package/dist/docs/api/FunstackStatic.md +184 -0
- package/dist/docs/index.md +22 -0
- package/dist/docs/learn/HowItWorks.md +109 -0
- package/dist/docs/learn/LazyServerComponents.md +120 -0
- package/dist/docs/learn/OptimizingPayloads.md +107 -0
- package/dist/docs/learn/RSC.md +179 -0
- package/dist/docs/learn/SSR.md +104 -0
- package/dist/entries/client.d.mts +1 -1
- package/dist/entries/rsc-client.d.mts +2 -2
- package/dist/entries/rsc-client.mjs +2 -2
- package/dist/entries/server.d.mts +2 -2
- package/dist/plugin/index.d.mts +17 -1
- package/dist/plugin/index.d.mts.map +1 -1
- package/dist/plugin/index.mjs +10 -1
- package/dist/plugin/index.mjs.map +1 -1
- package/dist/rsc/defer.d.mts +18 -3
- package/dist/rsc/defer.d.mts.map +1 -1
- package/dist/rsc/defer.mjs +34 -14
- package/dist/rsc/defer.mjs.map +1 -1
- package/dist/rsc/entry.d.mts.map +1 -1
- package/dist/rsc/entry.mjs +85 -20
- package/dist/rsc/entry.mjs.map +1 -1
- package/dist/rsc-client/clientWrapper.d.mts +3 -3
- package/dist/rsc-client/clientWrapper.d.mts.map +1 -1
- package/dist/rsc-client/clientWrapper.mjs +2 -2
- package/dist/rsc-client/clientWrapper.mjs.map +1 -1
- package/dist/rsc-client/entry.d.mts +1 -1
- package/dist/rsc-client/entry.mjs +1 -1
- package/dist/ssr/entry.d.mts +2 -0
- package/dist/ssr/entry.d.mts.map +1 -1
- package/dist/ssr/entry.mjs +6 -2
- package/dist/ssr/entry.mjs.map +1 -1
- package/package.json +26 -11
- package/skills/funstack-static-knowledge/SKILL.md +44 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
# Migrating from Vite SPA
|
|
2
|
+
|
|
3
|
+
Already have a Vite-powered React SPA? This guide walks you through migrating to FUNSTACK Static to unlock React Server Components and improved performance.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Migrating from a standard Vite React SPA to FUNSTACK Static involves:
|
|
8
|
+
|
|
9
|
+
1. Installing FUNSTACK Static
|
|
10
|
+
2. Adding the Vite plugin
|
|
11
|
+
3. Restructuring your entry point into Root and App components
|
|
12
|
+
4. Converting appropriate components to Server Components
|
|
13
|
+
|
|
14
|
+
The good news: your existing client components work as-is. You can migrate incrementally.
|
|
15
|
+
|
|
16
|
+
## Step 1: Install Dependencies
|
|
17
|
+
|
|
18
|
+
Add FUNSTACK Static to your existing project:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @funstack/static
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or with pnpm:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pnpm add @funstack/static
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
**Hint:** at this point, a skill installer command is available to add FUNSTACK Static knowledge to your AI agents. Run `npx funstack-static-skill-installer` or similar to add the skill. Then you can ask your AI assistant for help with the migration!
|
|
31
|
+
|
|
32
|
+
## Step 2: Update Vite Config
|
|
33
|
+
|
|
34
|
+
Modify your `vite.config.ts` to add the FUNSTACK Static plugin:
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
import { funstackStatic } from "@funstack/static";
|
|
38
|
+
import react from "@vitejs/plugin-react";
|
|
39
|
+
import { defineConfig } from "vite";
|
|
40
|
+
|
|
41
|
+
export default defineConfig({
|
|
42
|
+
plugins: [
|
|
43
|
+
funstackStatic({
|
|
44
|
+
root: "./src/Root.tsx",
|
|
45
|
+
app: "./src/App.tsx",
|
|
46
|
+
}),
|
|
47
|
+
react(),
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Note that the existing React plugin remains; FUNSTACK Static works alongside it.
|
|
53
|
+
|
|
54
|
+
## Step 3: Create the Root Component
|
|
55
|
+
|
|
56
|
+
The Root component replaces your `index.html` file. Create `src/Root.tsx`:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// src/Root.tsx
|
|
60
|
+
import type React from "react";
|
|
61
|
+
|
|
62
|
+
export default function Root({ children }: { children: React.ReactNode }) {
|
|
63
|
+
return (
|
|
64
|
+
<html lang="en">
|
|
65
|
+
<head>
|
|
66
|
+
<meta charSet="UTF-8" />
|
|
67
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
68
|
+
<title>My App</title>
|
|
69
|
+
{/* Add your existing <head> content here */}
|
|
70
|
+
</head>
|
|
71
|
+
<body>
|
|
72
|
+
<div id="root">{children}</div>
|
|
73
|
+
</body>
|
|
74
|
+
</html>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Move any content from your `index.html` `<head>` section into this component.
|
|
80
|
+
|
|
81
|
+
## Step 4: Update Your App Entry Point
|
|
82
|
+
|
|
83
|
+
Your existing `main.tsx` or `index.tsx` likely looks like this:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
// Before: src/main.tsx
|
|
87
|
+
import React from "react";
|
|
88
|
+
import ReactDOM from "react-dom/client";
|
|
89
|
+
import App from "./App";
|
|
90
|
+
import "./index.css";
|
|
91
|
+
|
|
92
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
93
|
+
<React.StrictMode>
|
|
94
|
+
<App />
|
|
95
|
+
</React.StrictMode>,
|
|
96
|
+
);
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
With FUNSTACK Static, you no longer need this file. Instead, your `App.tsx` becomes the entry point and is automatically rendered as a Server Component:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
// After: src/App.tsx
|
|
103
|
+
import "./index.css";
|
|
104
|
+
import { HomePage } from "./pages/HomePage";
|
|
105
|
+
|
|
106
|
+
export default function App() {
|
|
107
|
+
return <HomePage />;
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
You can delete `main.tsx` - FUNSTACK Static handles the rendering.
|
|
112
|
+
|
|
113
|
+
## Step 5: Mark Client Component Boundaries
|
|
114
|
+
|
|
115
|
+
In a standard Vite SPA, all components are client components. With FUNSTACK Static, components are Server Components by default.
|
|
116
|
+
|
|
117
|
+
As a starting point, it is possible to mark the App component as a client component by adding `"use client"` at the top:
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
// src/App.tsx
|
|
121
|
+
"use client";
|
|
122
|
+
// ...rest of the code
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This makes the entire app a client component, similar to your previous SPA.
|
|
126
|
+
|
|
127
|
+
However, to take advantage of Server Components, you should incrementally convert components to Server Components. This can be done by removing `"use client"` from components that don't need it and instead adding it only to components that require client-side interactivity.
|
|
128
|
+
|
|
129
|
+
**Note:** you only need to add `"use client"` to components that are **directly imported by Server Components**. This marks the boundary between server and client code. Components imported by other client components don't need the directive.
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
// src/components/Counter.tsx
|
|
133
|
+
"use client";
|
|
134
|
+
|
|
135
|
+
import { useState } from "react";
|
|
136
|
+
import { Button } from "./Button"; // No "use client" needed in Button.tsx
|
|
137
|
+
|
|
138
|
+
export function Counter() {
|
|
139
|
+
const [count, setCount] = useState(0);
|
|
140
|
+
return <Button onClick={() => setCount(count + 1)}>Count: {count}</Button>;
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
In this example, `Counter.tsx` needs `"use client"` because it's imported by a Server Component. But `Button.tsx` doesn't need it since it's only imported by `Counter`, which is already a client component.
|
|
145
|
+
|
|
146
|
+
A component is a client component if it:
|
|
147
|
+
|
|
148
|
+
- Use client-only hooks (`useState`, `useEffect`, `useContext`, etc.)
|
|
149
|
+
- Attach event handlers (`onClick`, `onChange`, etc.)
|
|
150
|
+
- Use browser-only APIs (`window`, `document`, `localStorage`, etc.)
|
|
151
|
+
|
|
152
|
+
## Step 6: Update Your Router (If Applicable)
|
|
153
|
+
|
|
154
|
+
If you're using React Router or another client-side router, you have two options:
|
|
155
|
+
|
|
156
|
+
### Option A: Keep Your Existing Router
|
|
157
|
+
|
|
158
|
+
You can continue using your existing router as a client component:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
// src/App.tsx
|
|
162
|
+
import { ClientRouter } from "./ClientRouter";
|
|
163
|
+
|
|
164
|
+
export default function App() {
|
|
165
|
+
return <ClientRouter />;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
// src/ClientRouter.tsx
|
|
171
|
+
"use client";
|
|
172
|
+
|
|
173
|
+
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
|
174
|
+
import { Home } from "./pages/Home";
|
|
175
|
+
import { About } from "./pages/About";
|
|
176
|
+
|
|
177
|
+
export function ClientRouter() {
|
|
178
|
+
return (
|
|
179
|
+
<BrowserRouter>
|
|
180
|
+
<Routes>
|
|
181
|
+
<Route path="/" element={<Home />} />
|
|
182
|
+
<Route path="/about" element={<About />} />
|
|
183
|
+
</Routes>
|
|
184
|
+
</BrowserRouter>
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Option B: Use a Server-Compatible Router
|
|
190
|
+
|
|
191
|
+
For better performance, consider migrating to [FUNSTACK Router](https://github.com/uhyo/funstack-router) which is a standalone router for SPAs but still integrates well with Server Components.
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
npm install @funstack/router
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
```tsx
|
|
198
|
+
// src/App.tsx
|
|
199
|
+
import { Router } from "@funstack/router";
|
|
200
|
+
import { route } from "@funstack/router/server";
|
|
201
|
+
import { Home } from "./pages/Home";
|
|
202
|
+
import { About } from "./pages/About";
|
|
203
|
+
|
|
204
|
+
const routes = [
|
|
205
|
+
route({ path: "/", component: <Home /> }),
|
|
206
|
+
route({ path: "/about", component: <About /> }),
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
export default function App() {
|
|
210
|
+
return <Router routes={routes} />;
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Step 7: Delete Unnecessary Files
|
|
215
|
+
|
|
216
|
+
After migration, you can remove:
|
|
217
|
+
|
|
218
|
+
- `src/main.tsx` (or `src/index.tsx`) - no longer needed
|
|
219
|
+
- `index.html` - replaced by `Root.tsx`
|
|
220
|
+
|
|
221
|
+
## Common Migration Patterns
|
|
222
|
+
|
|
223
|
+
### Global Styles
|
|
224
|
+
|
|
225
|
+
Import global CSS in your `App.tsx`:
|
|
226
|
+
|
|
227
|
+
```tsx
|
|
228
|
+
// src/App.tsx
|
|
229
|
+
import "./index.css";
|
|
230
|
+
import "./global.css";
|
|
231
|
+
|
|
232
|
+
export default function App() {
|
|
233
|
+
// ...
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### Context Providers
|
|
238
|
+
|
|
239
|
+
Wrap client-side providers in a client component:
|
|
240
|
+
|
|
241
|
+
```tsx
|
|
242
|
+
// src/Providers.tsx
|
|
243
|
+
"use client";
|
|
244
|
+
|
|
245
|
+
import { ThemeProvider } from "./ThemeContext";
|
|
246
|
+
import { AuthProvider } from "./AuthContext";
|
|
247
|
+
|
|
248
|
+
export function Providers({ children }: { children: React.ReactNode }) {
|
|
249
|
+
return (
|
|
250
|
+
<ThemeProvider>
|
|
251
|
+
<AuthProvider>{children}</AuthProvider>
|
|
252
|
+
</ThemeProvider>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
```tsx
|
|
258
|
+
// src/App.tsx
|
|
259
|
+
import { Providers } from "./Providers";
|
|
260
|
+
import { HomePage } from "./pages/HomePage";
|
|
261
|
+
|
|
262
|
+
export default function App() {
|
|
263
|
+
return (
|
|
264
|
+
<Providers>
|
|
265
|
+
<HomePage />
|
|
266
|
+
</Providers>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Environment Variables
|
|
272
|
+
|
|
273
|
+
Client-side environment variables still work the same way with the `VITE_` prefix:
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
// In client components
|
|
277
|
+
const apiUrl = import.meta.env.VITE_API_URL;
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
## Verifying the Migration
|
|
281
|
+
|
|
282
|
+
Run the development server:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
npm run dev
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Check that:
|
|
289
|
+
|
|
290
|
+
- Your app loads correctly
|
|
291
|
+
- Interactive components work (clicks, form inputs, etc.)
|
|
292
|
+
- Routing works as expected
|
|
293
|
+
- Styles are applied correctly
|
|
294
|
+
|
|
295
|
+
Build for production:
|
|
296
|
+
|
|
297
|
+
```bash
|
|
298
|
+
npm run build
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Your static files will be generated in `dist/public`, ready for deployment.
|
|
302
|
+
|
|
303
|
+
## Troubleshooting
|
|
304
|
+
|
|
305
|
+
### "Cannot use hooks in Server Component"
|
|
306
|
+
|
|
307
|
+
Add `"use client"` at the top of components that use React hooks.
|
|
308
|
+
|
|
309
|
+
### "window is not defined"
|
|
310
|
+
|
|
311
|
+
Components using browser APIs must be client components. Add `"use client"` directive.
|
|
312
|
+
|
|
313
|
+
### Styles not loading
|
|
314
|
+
|
|
315
|
+
Ensure CSS imports are in `App.tsx` or in client components that are actually rendered.
|
|
316
|
+
|
|
317
|
+
## What's Next?
|
|
318
|
+
|
|
319
|
+
- Learn about [defer()](/funstack-static/api/defer) for code splitting Server Components
|
|
320
|
+
- Explore [Optimizing RSC Payloads](/funstack-static/learn/optimizing-payloads) for better performance
|
|
321
|
+
- Understand [How It Works](/funstack-static/learn/how-it-works) under the hood
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# defer()
|
|
2
|
+
|
|
3
|
+
The `defer()` function enables deferred rendering for React Server Components, reducing initial data load.
|
|
4
|
+
|
|
5
|
+
You can think of this as React's `lazy` API but for Server Components.
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
// @funstack/static/server is where utilities for server components live
|
|
11
|
+
import { defer } from "@funstack/static/server";
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Usage
|
|
15
|
+
|
|
16
|
+
```tsx
|
|
17
|
+
import { defer } from "@funstack/static/server";
|
|
18
|
+
import { HeavyServerComponent } from "./HeavyServerComponent";
|
|
19
|
+
|
|
20
|
+
function Page() {
|
|
21
|
+
return (
|
|
22
|
+
<details>
|
|
23
|
+
<summary>Very long description</summary>
|
|
24
|
+
<Suspense fallback={<p>Loading...</p>}>
|
|
25
|
+
{defer(<HeavyServerComponent />)}
|
|
26
|
+
</Suspense>
|
|
27
|
+
</details>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
By using `defer()`, the `HeavyServerComponent` will still be rendered on the server (during build), but its data will be sent to the client as a separate RSC payload.
|
|
33
|
+
|
|
34
|
+
This means that:
|
|
35
|
+
|
|
36
|
+
- Client can start rendering the rest of the page without waiting for `HeavyServerComponent`'s data
|
|
37
|
+
- When the `defer(<HeavyServerComponent />)` part is rendered on the client, it will fetch the separate RSC payload and show the content.
|
|
38
|
+
|
|
39
|
+
The key point is that `HeavyServerComponent` is still a Server Component, so only the rendered HTML (and usage of Client Components inside it) is sent to the client, not the component code itself.
|
|
40
|
+
|
|
41
|
+
**Note:** `defer()` must be used inside a `Suspense` boundary since the content will be streamed in later.
|
|
42
|
+
|
|
43
|
+
## Signature
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
export function defer(element: ReactElement, options?: DeferOptions): ReactNode;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Parameters
|
|
50
|
+
|
|
51
|
+
- **element:** A JSX element (Server Component) to render with deferred loading.
|
|
52
|
+
- **options:** (optional) Configuration options for the deferred payload.
|
|
53
|
+
|
|
54
|
+
### DeferOptions
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
interface DeferOptions {
|
|
58
|
+
/**
|
|
59
|
+
* Optional name for debugging purposes.
|
|
60
|
+
* In development: included in the RSC payload file name.
|
|
61
|
+
* In production: logged when the payload file is emitted.
|
|
62
|
+
*/
|
|
63
|
+
name?: string;
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
- **name:** An optional identifier to help with debugging. When provided:
|
|
68
|
+
- In development mode, the name is included in the RSC payload file name (e.g., `/funstack__/fun:rsc-payload/HomePage-b5698be72eea3c37`)
|
|
69
|
+
- In production mode, the name is logged when the payload file is emitted
|
|
70
|
+
|
|
71
|
+
### Returns
|
|
72
|
+
|
|
73
|
+
A React Node that will stream its content separately from the main entry point.
|
|
74
|
+
|
|
75
|
+
## When to Use defer()
|
|
76
|
+
|
|
77
|
+
Use `defer()` when you have components that:
|
|
78
|
+
|
|
79
|
+
- Renders large HTML content
|
|
80
|
+
- Is not immediately visible on page load (e.g., inside a collapsed section)
|
|
81
|
+
|
|
82
|
+
Typically, you will want to wrap route components with `defer()` to improve initial load performance. Otherwise, user needs to wait for contents for all pages to arrive before seeing anything.
|
|
83
|
+
|
|
84
|
+
```tsx
|
|
85
|
+
import { defer } from "@funstack/static/server";
|
|
86
|
+
import HomePage from "./HomePage";
|
|
87
|
+
import AboutPage from "./AboutPage";
|
|
88
|
+
|
|
89
|
+
const routes = [
|
|
90
|
+
route({
|
|
91
|
+
path: "/",
|
|
92
|
+
component: defer(<HomePage />, { name: "HomePage" }),
|
|
93
|
+
}),
|
|
94
|
+
route({
|
|
95
|
+
path: "/about",
|
|
96
|
+
component: defer(<AboutPage />, { name: "AboutPage" }),
|
|
97
|
+
}),
|
|
98
|
+
// ...
|
|
99
|
+
];
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Using the `name` option makes it easier to identify which deferred component corresponds to which payload file during development and in build logs.
|
|
103
|
+
|
|
104
|
+
## How It Works
|
|
105
|
+
|
|
106
|
+
By default, FUNSTACK Static puts the entire app (`<App />`) into one RSC payload (`/funstack__/index.txt`). The client fetches this payload to render your SPA.
|
|
107
|
+
|
|
108
|
+
When you use `defer(<Component />)`, FUNSTACK Static creates **additional RSC payloads** for the rendering result of the element. This results in an additional emit of RSC payload files like `/funstack__/fun:rsc-payload/b5698be72eea3c37`. If you provide a `name` option, the file name will include it (e.g., `/funstack__/fun:rsc-payload/HomePage-b5698be72eea3c37`).
|
|
109
|
+
|
|
110
|
+
In the main RSC payload, the `defer` call is replaced with a client component `<DeferredComponent moduleId="fun:rsc-payload/b5698be72eea3c37" />`. This component is responsible for fetching the additional RSC payload from client and renders it when it's ready.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# funstackStatic()
|
|
2
|
+
|
|
3
|
+
The `funstackStatic()` function is the main Vite plugin that enables React Server Components for building SPAs without a runtime server.
|
|
4
|
+
|
|
5
|
+
## Import
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import funstackStatic from "@funstack/static";
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// vite.config.ts
|
|
15
|
+
import funstackStatic from "@funstack/static";
|
|
16
|
+
import { defineConfig } from "vite";
|
|
17
|
+
|
|
18
|
+
export default defineConfig({
|
|
19
|
+
plugins: [
|
|
20
|
+
funstackStatic({
|
|
21
|
+
root: "./src/root.tsx",
|
|
22
|
+
app: "./src/App.tsx",
|
|
23
|
+
}),
|
|
24
|
+
],
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Options
|
|
29
|
+
|
|
30
|
+
### root (required)
|
|
31
|
+
|
|
32
|
+
**Type:** `string`
|
|
33
|
+
|
|
34
|
+
Path to the root component file. This component wraps your entire application and defines the HTML document structure (`<html>`, `<head>`, `<body>`).
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
funstackStatic({
|
|
38
|
+
root: "./src/root.tsx",
|
|
39
|
+
// ...
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The root component receives `children` as a prop:
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// src/root.tsx
|
|
47
|
+
export default function Root({ children }: { children: React.ReactNode }) {
|
|
48
|
+
return (
|
|
49
|
+
<html lang="en">
|
|
50
|
+
<head>
|
|
51
|
+
<meta charSet="UTF-8" />
|
|
52
|
+
<title>My Site</title>
|
|
53
|
+
</head>
|
|
54
|
+
<body>{children}</body>
|
|
55
|
+
</html>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### app (required)
|
|
61
|
+
|
|
62
|
+
**Type:** `string`
|
|
63
|
+
|
|
64
|
+
Path to the app component file. This component defines your application's content.
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
funstackStatic({
|
|
68
|
+
root: "./src/root.tsx",
|
|
69
|
+
app: "./src/App.tsx",
|
|
70
|
+
});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
```tsx
|
|
74
|
+
// src/App.tsx
|
|
75
|
+
|
|
76
|
+
export default function App() {
|
|
77
|
+
return (
|
|
78
|
+
<div>
|
|
79
|
+
<h1>Welcome to My Site</h1>
|
|
80
|
+
{/* Your fantastic application goes here */}
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
**Note:** if your app has multiple pages, you can use a routing library here just like in a traditional SPA.
|
|
87
|
+
|
|
88
|
+
### publicOutDir (optional)
|
|
89
|
+
|
|
90
|
+
**Type:** `string`
|
|
91
|
+
**Default:** `"dist/public"`
|
|
92
|
+
|
|
93
|
+
Output directory for the generated static files.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
funstackStatic({
|
|
97
|
+
root: "./src/root.tsx",
|
|
98
|
+
app: "./src/App.tsx",
|
|
99
|
+
publicOutDir: "build/static",
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### ssr (optional)
|
|
104
|
+
|
|
105
|
+
**Type:** `boolean`
|
|
106
|
+
**Default:** `false`
|
|
107
|
+
|
|
108
|
+
Enable server-side rendering of the App component.
|
|
109
|
+
|
|
110
|
+
When `false` (default), only the Root shell is rendered to HTML at build time. The App component's RSC payload is fetched separately and rendered client-side using `createRoot`. This results in faster initial HTML delivery but requires JavaScript to display the App content.
|
|
111
|
+
|
|
112
|
+
When `true`, both the Root and App components are fully rendered to HTML. The client hydrates the existing HTML using `hydrateRoot`, which can improve perceived performance and SEO since the full content is visible before JavaScript loads.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
funstackStatic({
|
|
116
|
+
root: "./src/root.tsx",
|
|
117
|
+
app: "./src/App.tsx",
|
|
118
|
+
ssr: true, // Enable full SSR
|
|
119
|
+
});
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Note:** In both modes, React Server Components are used - the `ssr` option only controls whether the App's HTML is pre-rendered or rendered client-side.
|
|
123
|
+
|
|
124
|
+
### clientInit (optional)
|
|
125
|
+
|
|
126
|
+
**Type:** `string`
|
|
127
|
+
|
|
128
|
+
Path to a module that runs on the client side **before React hydration**. Use this for client-side instrumentation like Sentry, analytics, or feature flags.
|
|
129
|
+
|
|
130
|
+
The module is imported for its side effects only - no exports are needed.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
funstackStatic({
|
|
134
|
+
root: "./src/root.tsx",
|
|
135
|
+
app: "./src/App.tsx",
|
|
136
|
+
clientInit: "./src/client-init.ts",
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Example client init file:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
// src/client-init.ts
|
|
144
|
+
import * as Sentry from "@sentry/browser";
|
|
145
|
+
|
|
146
|
+
Sentry.init({
|
|
147
|
+
dsn: "https://your-sentry-dsn",
|
|
148
|
+
environment: import.meta.env.MODE,
|
|
149
|
+
});
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
**Note:** Errors in the client init module will propagate normally and prevent the app from rendering.
|
|
153
|
+
|
|
154
|
+
## Full Example
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
// vite.config.ts
|
|
158
|
+
import { funstackStatic } from "@funstack/static";
|
|
159
|
+
import { defineConfig } from "vite";
|
|
160
|
+
|
|
161
|
+
export default defineConfig({
|
|
162
|
+
plugins: [
|
|
163
|
+
funstackStatic({
|
|
164
|
+
root: "./src/root.tsx",
|
|
165
|
+
app: "./src/App.tsx",
|
|
166
|
+
publicOutDir: "dist/public",
|
|
167
|
+
}),
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Vite Commands
|
|
173
|
+
|
|
174
|
+
You can use the same Vite commands you would use in a normal Vite project:
|
|
175
|
+
|
|
176
|
+
- `vite dev` - Starts the development server
|
|
177
|
+
- `vite build` - Builds the static files
|
|
178
|
+
- `vite preview` - Previews the built static files locally
|
|
179
|
+
|
|
180
|
+
## See Also
|
|
181
|
+
|
|
182
|
+
- [Getting Started](/funstack-static/getting-started) - Quick start guide
|
|
183
|
+
- [defer()](/funstack-static/api/defer) - Deferred rendering for streaming
|
|
184
|
+
- [React Server Components](/funstack-static/learn/rsc) - Understanding RSC
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# @funstack/static Documentation
|
|
2
|
+
|
|
3
|
+
A Vite plugin for building static sites with React Server Components.
|
|
4
|
+
|
|
5
|
+
## Available Documentation
|
|
6
|
+
|
|
7
|
+
- [FAQ](./FAQ.md) - This is a bug in React itself. Please wait for [the fix](https://github.com/facebook/react/pull/34760) to be released.
|
|
8
|
+
- [Getting Started](./GettingStarted.md) - Welcome to **FUNSTACK Static**! Build high-performance Single Page Applications powered by React Server Components - no server required at runtime.
|
|
9
|
+
- [Migrating from Vite SPA](./MigratingFromViteSPA.md) - Already have a Vite-powered React SPA? This guide walks you through migrating to FUNSTACK Static to unlock React Server Components and improved performance.
|
|
10
|
+
|
|
11
|
+
### API
|
|
12
|
+
|
|
13
|
+
- [defer()](./api/Defer.md) - The `defer()` function enables deferred rendering for React Server Components, reducing initial data load.
|
|
14
|
+
- [funstackStatic()](./api/FunstackStatic.md) - The `funstackStatic()` function is the main Vite plugin that enables React Server Components for building SPAs without a runtime server.
|
|
15
|
+
|
|
16
|
+
### Learn
|
|
17
|
+
|
|
18
|
+
- [How It Works](./learn/HowItWorks.md) - FUNSTACK Static is a React framework that leverages React Server Components (RSC) to build a fully static Single Page Application. The result is a set of files that can be deployed to **any static file hosting service** - no server required at runtime.
|
|
19
|
+
- [Using lazy() in Server Components](./learn/LazyServerComponents.md) - React's `lazy()` API is typically associated with client-side code splitting. However, it can also be used in server environments to reduce the initial response time of the development server by deferring the work needed to compute your application.
|
|
20
|
+
- [Optimizing RSC Payloads](./learn/OptimizingPayloads.md) - FUNSTACK Static uses React Server Components (RSC) to pre-render your application at build time. By default, all content is bundled into a single RSC payload. This page explains how to split that payload into smaller chunks for better loading performance.
|
|
21
|
+
- [React Server Components](./learn/RSC.md) - [React Server Components (RSC)](https://react.dev/reference/rsc/server-components) are a new paradigm for building React applications where components can run on the server (or at build time) rather than in the browser.
|
|
22
|
+
- [Server-Side Rendering](./learn/SSR.md) - In FUNSTACK Static, **Server-Side Rendering (SSR)** means a build-time process that pre-renders your React components (including client components) to HTML. This can make the initial paint faster.
|