@funstack/static 0.0.3 → 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 CHANGED
@@ -2,9 +2,6 @@
2
2
 
3
3
  A maximally minimal React framework. Vite plugin for static sites with RSC support.
4
4
 
5
- > [!WARNING]
6
- > This is work in progress.
7
-
8
5
  ## Features
9
6
 
10
7
  - :x: **No server runs** - perfect for CSR (Client Side Rendering) app and static deployment.
@@ -38,22 +35,22 @@ export default defineConfig({
38
35
  });
39
36
  ```
40
37
 
41
- ## CLI Commands
38
+ ## Documentation
42
39
 
43
- ### `funstack-static-skill-installer`
40
+ For detailed API documentation and guides, visit the **[Documentation](https://uhyo.github.io/funstack-static/)**.
44
41
 
45
- Installs the `funstack-static-knowledge` skill for AI coding assistants (like [Claude Code](https://docs.anthropic.com/en/docs/claude-code)).
42
+ ### :robot: FUNSTACK Static Skill
43
+
44
+ FUNSTACK Static provides an Agent Skill to feed your AI agents with knowledge about this framework. After installing `@funstack/static`, run the following command to add the skill to the project:
46
45
 
47
46
  ```sh
48
47
  npx funstack-static-skill-installer
48
+ # or
49
+ yarn funstack-static-skill-installer
50
+ # or
51
+ pnpm funstack-static-skill-installer
49
52
  ```
50
53
 
51
- This command registers the skill that provides AI assistants with knowledge about the FUNSTACK Static framework, including API references, best practices, and architectural guidance. After installation, your AI assistant will be able to better understand and work with your FUNSTACK Static project.
52
-
53
- ## Documentation
54
-
55
- For detailed API documentation and guides, visit the **[Documentation](https://uhyo.github.io/funstack-static/)**.
56
-
57
54
  ## License
58
55
 
59
56
  MIT
@@ -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
@@ -6,6 +6,7 @@ A Vite plugin for building static sites with React Server Components.
6
6
 
7
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
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.
9
10
 
10
11
  ### API
11
12
 
@@ -15,6 +16,7 @@ A Vite plugin for building static sites with React Server Components.
15
16
  ### Learn
16
17
 
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.
18
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.
19
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.
20
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.
@@ -0,0 +1,120 @@
1
+ # Using lazy() in Server Components
2
+
3
+ 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.
4
+
5
+ ## The Problem: Development Server Latency
6
+
7
+ When the development server receives a request, it needs to compute `<App />` to generate the response. This computation requires loading all imported modules, even those for contents that are not needed for the current request. For example, consider a routing setup like this:
8
+
9
+ ```tsx
10
+ // All these imports are loaded immediately
11
+ import HomePage from "./pages/Home";
12
+ import AboutPage from "./pages/About";
13
+ import DocsPage from "./pages/Docs";
14
+ import SettingsPage from "./pages/Settings";
15
+ // ... more page imports
16
+
17
+ const routes = [
18
+ route({ path: "/", component: defer(<HomePage />) }),
19
+ route({ path: "/about", component: defer(<AboutPage />) }),
20
+ // ... more routes
21
+ ];
22
+
23
+ export default function App() {
24
+ return <Router routes={routes} />;
25
+ }
26
+ ```
27
+
28
+ In a large application with many routes, this upfront loading adds latency to the first request, even though only one route's component will actually render.
29
+
30
+ While use of [defer()](./optimizing-payloads) helps reducing initial load by deferring _rendering_ of route components, the work to _import_ those components still happens immediately.
31
+
32
+ ## How lazy() Helps
33
+
34
+ `lazy()` defers the import of a module until the component is actually rendered. In a server environment, this means:
35
+
36
+ 1. The initial `<App />` computation only loads the routing structure
37
+ 2. Page components are loaded on-demand when their route matches
38
+ 3. Unused route components are never loaded for that request
39
+
40
+ ```tsx
41
+ import { lazy } from "react";
42
+
43
+ // These imports are deferred
44
+ const HomePage = lazy(() => import("./pages/Home"));
45
+ const AboutPage = lazy(() => import("./pages/About"));
46
+ const DocsPage = lazy(() => import("./pages/Docs"));
47
+ const SettingsPage = lazy(() => import("./pages/Settings"));
48
+
49
+ const routes = [
50
+ route({ path: "/", component: defer(<HomePage />) }),
51
+ route({ path: "/about", component: defer(<AboutPage />) }),
52
+ // ... more routes
53
+ ];
54
+ ```
55
+
56
+ When a user visits `/about`, only `AboutPage` is actually imported. The other page modules remain unloaded, reducing the work the server needs to do.
57
+
58
+ ## Combining with `defer()`
59
+
60
+ To use `lazy()` effectively in server components, you should combine it with `defer()`.
61
+
62
+ Without `defer()`, the contents of the lazy-loaded components would be part of the initial RSC payload. This means the server would still need to fully render them before sending the response, negating the benefits of lazy loading.
63
+
64
+ ## Example
65
+
66
+ Here's a complete example of using `lazy()` with FUNSTACK Router as a router library:
67
+
68
+ ```tsx
69
+ import { lazy, Suspense } from "react";
70
+ import { Outlet } from "@funstack/router";
71
+ import { route } from "@funstack/router/server";
72
+ import { defer } from "@funstack/static/server";
73
+ import { Layout } from "./components/Layout";
74
+
75
+ // Lazy load page components
76
+ const HomePage = lazy(() => import("./pages/Home"));
77
+ const AboutPage = lazy(() => import("./pages/About"));
78
+ const DocsPage = lazy(() => import("./pages/Docs"));
79
+
80
+ const routes = [
81
+ route({
82
+ path: "/",
83
+ component: (
84
+ <Layout>
85
+ <Outlet />
86
+ </Layout>
87
+ ),
88
+ children: [
89
+ route({
90
+ path: "/",
91
+ component: defer(<HomePage />),
92
+ }),
93
+ route({
94
+ path: "/about",
95
+ component: defer(<AboutPage />),
96
+ }),
97
+ route({
98
+ path: "/docs",
99
+ component: defer(<DocsPage />),
100
+ }),
101
+ ],
102
+ }),
103
+ ];
104
+ ```
105
+
106
+ ## When to Use This Pattern
107
+
108
+ This optimization is most beneficial when:
109
+
110
+ - **You have many routes** - The more unused routes you can skip loading, the bigger the win
111
+ - **Page components have heavy dependencies** - If a page imports large libraries, deferring that import saves significant work
112
+ - **Development server responsiveness matters** - This pattern primarily improves development experience; production builds pre-render everything anyway
113
+
114
+ For small applications with few routes, the overhead of `lazy()` may not be worth it. But as your application grows, lazy loading routes becomes increasingly valuable.
115
+
116
+ ## See Also
117
+
118
+ - [Optimizing RSC Payloads](/funstack-static/learn/optimizing-payloads) - Using `defer()` to split RSC payloads
119
+ - [How It Works](/funstack-static/learn/how-it-works) - Overall FUNSTACK Static architecture
120
+ - [defer()](/funstack-static/api/defer) - API reference for the defer function
@@ -98,6 +98,8 @@ Each payload file has a **content-based hash** in its filename. This enables agg
98
98
 
99
99
  **Consider below-the-fold content** - Content hidden in collapsed sections, tabs, or modals is a good candidate for `defer()` since users may never need it.
100
100
 
101
+ > **Tip:** You can combine `defer()` with React 19's `<Activity>` component to prefetch content in the background. When a `defer()`ed node is rendered under `<Activity mode="hidden">`, it won't be shown in the UI, but the fetch of the RSC payload will start immediately. This is useful when hidden content (like inactive tabs or collapsed sections) should be fetched ahead of time so it's ready when the user needs it.
102
+
101
103
  ## See Also
102
104
 
103
105
  - [defer()](/funstack-static/api/defer) - API reference with full signature and technical details
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funstack/static",
3
- "version": "0.0.3",
3
+ "version": "0.0.4",
4
4
  "description": "FUNSTACK static library",
5
5
  "type": "module",
6
6
  "repository": {