@funstack/static 0.0.2 → 0.0.3
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 -0
- 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/api/Defer.md +110 -0
- package/dist/docs/api/FunstackStatic.md +184 -0
- package/dist/docs/index.md +20 -0
- package/dist/docs/learn/HowItWorks.md +109 -0
- package/dist/docs/learn/OptimizingPayloads.md +105 -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,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,20 @@
|
|
|
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
|
+
|
|
10
|
+
### API
|
|
11
|
+
|
|
12
|
+
- [defer()](./api/Defer.md) - The `defer()` function enables deferred rendering for React Server Components, reducing initial data load.
|
|
13
|
+
- [funstackStatic()](./api/FunstackStatic.md) - The `funstackStatic()` function is the main Vite plugin that enables React Server Components for building SPAs without a runtime server.
|
|
14
|
+
|
|
15
|
+
### Learn
|
|
16
|
+
|
|
17
|
+
- [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.
|
|
18
|
+
- [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
|
+
- [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
|
+
- [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,109 @@
|
|
|
1
|
+
# How It Works
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
FUNSTACK Static is built on top of [@vitejs/plugin-rsc](https://www.npmjs.com/package/@vitejs/plugin-rsc) for its RSC support.
|
|
6
|
+
|
|
7
|
+
## Architecture
|
|
8
|
+
|
|
9
|
+
FUNSTACK Static applications have **a single entrypoint** which is a server component. This component is responsible for rendering the entire application.
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
// src/App.tsx
|
|
13
|
+
export default function App() {
|
|
14
|
+
return (
|
|
15
|
+
<div>
|
|
16
|
+
<h1>Welcome to my FUNSTACK Static app!</h1>
|
|
17
|
+
{/* Your app components go here */}
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Currently, no routing solution is built in. If you need routing, you can use your favorite routing library for SPAs, such as [React Router](https://reactrouter.com/) (SPA mode) or [FUNSTACK Router](https://github.com/uhyo/funstack-router).
|
|
24
|
+
|
|
25
|
+
Server components cannot have any client-side interactivity. To add interactivity, you can use client components. FUNSTACK Static follows [the standard React Server Components conventions](https://react.dev/reference/rsc/server-components#adding-interactivity-to-server-components) (`"use client"`) for defining client components.
|
|
26
|
+
|
|
27
|
+
Behind the scenes, server components are rendered into **RSC payloads** at build time (or inside the development server in development mode). These payloads are then loaded by the client-side React application to reflect the server-rendered content to the DOM.
|
|
28
|
+
|
|
29
|
+
On production mode builds, FUNSTACK Static generates a set of static files that include:
|
|
30
|
+
|
|
31
|
+
- An `index.html` file that bootstraps the client-side React application
|
|
32
|
+
- JavaScript files for the client-side React application and its dependencies
|
|
33
|
+
- Asset files (CSS, images, etc.)
|
|
34
|
+
- RSC payload files generated from server components
|
|
35
|
+
|
|
36
|
+
Typically, the output structure looks like this:
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
dist/public
|
|
40
|
+
├── assets
|
|
41
|
+
│ ├── app-91R2BjDJ.css
|
|
42
|
+
│ ├── app-CQU2Svmn.js
|
|
43
|
+
│ ├── app-ovZqc1Hu.css
|
|
44
|
+
│ ├── index-CCEDZan_.js
|
|
45
|
+
│ ├── root-DvE5ENz2.css
|
|
46
|
+
│ └── rsc-D0fjt5Ie.js
|
|
47
|
+
├── funstack__
|
|
48
|
+
│ └── fun:rsc-payload
|
|
49
|
+
│ └── db1923b9b6507ab4.txt
|
|
50
|
+
└── index.html
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The RSC payload files under `funstack__` are loaded by the client-side code to bootstrap the application with server-rendered content.
|
|
54
|
+
|
|
55
|
+
This can been seen as an **optimized version of traditional client-only SPAs**, where the entire application is bundled into JavaScript files. By using RSC, some of the rendering work is offloaded to the build time, resulting in smaller JavaScript bundles combined with RSC payloads that require less client-side processing (parsing is easier, no JavaScript execution needed).
|
|
56
|
+
|
|
57
|
+
## Root Entry Point
|
|
58
|
+
|
|
59
|
+
The root entry point of a FUNSTACK Static application is defined in the `funstack.config.js` file using the `root` option. This is a **special endpoint** that defines the HTML shell of your application. This is a separate endpoint from the main App component.
|
|
60
|
+
|
|
61
|
+
A typical Root component looks like this:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// src/Root.tsx
|
|
65
|
+
export default function Root({ children }: { children: React.ReactNode }) {
|
|
66
|
+
return (
|
|
67
|
+
<html lang="en">
|
|
68
|
+
<head>
|
|
69
|
+
<meta charSet="UTF-8" />
|
|
70
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
71
|
+
<title>My FUNSTACK Static App</title>
|
|
72
|
+
</head>
|
|
73
|
+
<body>{children}</body>
|
|
74
|
+
</html>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The `children` prop contains the main App component.
|
|
80
|
+
|
|
81
|
+
The Root component is special in two ways:
|
|
82
|
+
|
|
83
|
+
1. Rendered into a fully static HTML file (`index.html`), not an RSC payload.
|
|
84
|
+
2. Is **not hydrated** on the client side. This means that any client-side interactivity (e.g., event handlers) inside the Root component will not work.
|
|
85
|
+
|
|
86
|
+
The Root entrypoint is a FUNSTACK Static counterpart to the `index.html` file in traditional SPAs. It allows you to still leverage some of the benefits of server components for defining the HTML shell of your application.
|
|
87
|
+
|
|
88
|
+
## Server-Side Rendering
|
|
89
|
+
|
|
90
|
+
By default, FUNSTACK Static only renders the Root shell to HTML. The App component is rendered client-side from its RSC payload. This behavior keeps the initial HTML small and fast to deliver.
|
|
91
|
+
|
|
92
|
+
If you want the App component to also be rendered to HTML (for better SEO or perceived performance), you can enable the `ssr` option:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
funstackStatic({
|
|
96
|
+
root: "./src/root.tsx",
|
|
97
|
+
app: "./src/App.tsx",
|
|
98
|
+
ssr: true,
|
|
99
|
+
});
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
With `ssr: true`, the full page content is visible in the HTML before JavaScript loads. The client then hydrates the existing HTML instead of rendering from scratch.
|
|
103
|
+
|
|
104
|
+
Note that in both modes, React Server Components are still used - the `ssr` option only controls whether the App's HTML is pre-rendered at build time or rendered client-side.
|
|
105
|
+
|
|
106
|
+
## See Also
|
|
107
|
+
|
|
108
|
+
- [React Server Components](/funstack-static/learn/rsc) - Understanding RSC in depth
|
|
109
|
+
- [Getting Started](/funstack-static/getting-started) - Set up your first project
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Optimizing RSC Payloads
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
## The Default Behavior
|
|
6
|
+
|
|
7
|
+
Without any optimization, your entire application is rendered into one RSC payload file:
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
dist/public/funstack__/
|
|
11
|
+
└── fun:rsc-payload/
|
|
12
|
+
└── b62ec6668fd49300.txt ← Contains everything
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
This means users must download the entire payload before seeing any content - even if they only need one page of a multi-page application.
|
|
16
|
+
|
|
17
|
+
**Note:** RSC payloads contain the rendering results of server components only; client components and their JavaScript bundles are handled separately. However, it is still important to optimize RSC payload sizes because the recommended best practice is to keep as much of your UI as server components as possible.
|
|
18
|
+
|
|
19
|
+
## Chunking with defer()
|
|
20
|
+
|
|
21
|
+
The `defer()` function lets you split your application into multiple RSC payloads. Each `defer()` call creates a separate payload file that loads on-demand when that component renders.
|
|
22
|
+
|
|
23
|
+
```tsx
|
|
24
|
+
import { defer } from "@funstack/static/server";
|
|
25
|
+
|
|
26
|
+
// Instead of this:
|
|
27
|
+
<HeavyContent />
|
|
28
|
+
|
|
29
|
+
// Do this:
|
|
30
|
+
<Suspense fallback={<p>Loading...</p>}>
|
|
31
|
+
{defer(<HeavyContent />)}
|
|
32
|
+
</Suspense>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The content inside `defer()` is still rendered at build time, but it's stored in a separate file and fetched only when needed.
|
|
36
|
+
|
|
37
|
+
**Note:** use of `defer()` requires a `<Suspense>` boundary to handle the loading state while the payload is being fetched.
|
|
38
|
+
|
|
39
|
+
## Route-Level Optimization
|
|
40
|
+
|
|
41
|
+
The most impactful use of `defer()` is wrapping your route components. This ensures users only download the payload for the page they're viewing:
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { defer } from "@funstack/static/server";
|
|
45
|
+
import { route } from "@funstack/router/server";
|
|
46
|
+
import HomePage from "./pages/Home";
|
|
47
|
+
import AboutPage from "./pages/About";
|
|
48
|
+
import DocsPage from "./pages/Docs";
|
|
49
|
+
|
|
50
|
+
const routes = [
|
|
51
|
+
route({
|
|
52
|
+
path: "/",
|
|
53
|
+
component: defer(<HomePage />),
|
|
54
|
+
}),
|
|
55
|
+
route({
|
|
56
|
+
path: "/about",
|
|
57
|
+
component: defer(<AboutPage />),
|
|
58
|
+
}),
|
|
59
|
+
route({
|
|
60
|
+
path: "/docs",
|
|
61
|
+
component: defer(<DocsPage />),
|
|
62
|
+
}),
|
|
63
|
+
];
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
With this setup:
|
|
67
|
+
|
|
68
|
+
- Visiting `/` only downloads the Home page payload
|
|
69
|
+
- Navigating to `/about` fetches the About page payload on-demand
|
|
70
|
+
- Users see content faster because they're not waiting for pages they haven't visited
|
|
71
|
+
|
|
72
|
+
## Output Structure
|
|
73
|
+
|
|
74
|
+
After building with route-level `defer()`, your output looks like this:
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
dist/public/funstack__/
|
|
78
|
+
└── fun:rsc-payload/
|
|
79
|
+
├── a3f2b1c9d8e7f6a5.txt ← Home page
|
|
80
|
+
├── b5698be72eea3c37.txt ← About page
|
|
81
|
+
├── b62ec6668fd49300.txt ← Main app shell
|
|
82
|
+
└── c7d8e9f0a1b2c3d4.txt ← Docs page
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Each payload file has a **content-based hash** in its filename. This enables aggressive browser caching - the file only changes when its content changes.
|
|
86
|
+
|
|
87
|
+
**Note:** during development, UUIDs are used instead of content hashes for faster rebuilds. Content hashes are applied during production builds.
|
|
88
|
+
|
|
89
|
+
## Best Practices
|
|
90
|
+
|
|
91
|
+
**Always wrap route components** - This is the single most important optimization. It prevents users from downloading content for pages they may never visit.
|
|
92
|
+
|
|
93
|
+
**Use Suspense boundaries** - Every `defer()` call must be inside a `<Suspense>` boundary. The fallback is shown while the payload is being fetched.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
<Suspense fallback={<PageSkeleton />}>{defer(<PageContent />)}</Suspense>
|
|
97
|
+
```
|
|
98
|
+
|
|
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
|
+
|
|
101
|
+
## See Also
|
|
102
|
+
|
|
103
|
+
- [defer()](/funstack-static/api/defer) - API reference with full signature and technical details
|
|
104
|
+
- [How It Works](/funstack-static/learn/how-it-works) - Overall FUNSTACK Static architecture
|
|
105
|
+
- [React Server Components](/funstack-static/learn/rsc) - Understanding RSC fundamentals
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# React Server Components
|
|
2
|
+
|
|
3
|
+
[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.
|
|
4
|
+
|
|
5
|
+
## What Are Server Components?
|
|
6
|
+
|
|
7
|
+
Server Components are React components that:
|
|
8
|
+
|
|
9
|
+
- **Run on the server/build time** - Not in the browser
|
|
10
|
+
- **Can be async** - Use `async/await` directly in components
|
|
11
|
+
- **Have zero client bundle impact** - Their code never ships to the browser
|
|
12
|
+
- **Can access server-side resources** - Files, databases, environment variables
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
// This component runs at build time, not in the browser
|
|
16
|
+
async function UserList() {
|
|
17
|
+
// Direct file system access
|
|
18
|
+
const data = await fs.readFile("./data/users.json", "utf-8");
|
|
19
|
+
const users = JSON.parse(data);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<ul>
|
|
23
|
+
{users.map((user) => (
|
|
24
|
+
<li key={user.id}>{user.name}</li>
|
|
25
|
+
))}
|
|
26
|
+
</ul>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## FUNSTACK Static and RSC
|
|
32
|
+
|
|
33
|
+
FUNSTACK Static is a React framework **without a runtime server** that still leverages React Server Components to **improve performance of traditional SPAs**.
|
|
34
|
+
|
|
35
|
+
All server components are rendered at **build time**, producing RSC Payloads that are fetched by the client (browser) at runtime.
|
|
36
|
+
|
|
37
|
+
While dynamic server-side logic isn't possible with FUNSTACK Static, you can still benefit from RSC features like:
|
|
38
|
+
|
|
39
|
+
- Reduced bundle size
|
|
40
|
+
- Build-time data fetching
|
|
41
|
+
- Async components
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
// Build-time data fetching with an async Server Component
|
|
45
|
+
async function BlogPost({ slug }: { slug: string }) {
|
|
46
|
+
// Runs during build, not at runtime
|
|
47
|
+
const content = await fetchMarkdownFile(`./posts/${slug}.md`);
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<article>
|
|
51
|
+
<Markdown content={content} />
|
|
52
|
+
</article>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
**Note:** in FUNSTACK Static, async data is fetched at build time. For truly dynamic data, you'll need client-side JavaScript and fetch data as you would do in pre-RSC SPAs.
|
|
58
|
+
|
|
59
|
+
## Composing Server and Client Components
|
|
60
|
+
|
|
61
|
+
While Server Components can't use hooks or browser APIs, they can render Client Components that do:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// Server Component (runs at build time)
|
|
65
|
+
async function ProductPage({ id }: { id: string }) {
|
|
66
|
+
const product = await fetchProduct(id);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<div>
|
|
70
|
+
<h1>{product.name}</h1>
|
|
71
|
+
<p>{product.description}</p>
|
|
72
|
+
|
|
73
|
+
{/* Client Component for interactivity */}
|
|
74
|
+
<AddToCartButton productId={id} />
|
|
75
|
+
</div>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
// Client Component (runs in browser)
|
|
82
|
+
"use client";
|
|
83
|
+
|
|
84
|
+
import { useState } from "react";
|
|
85
|
+
|
|
86
|
+
export function AddToCartButton({ productId }: { productId: string }) {
|
|
87
|
+
const [added, setAdded] = useState(false);
|
|
88
|
+
|
|
89
|
+
return (
|
|
90
|
+
<button onClick={() => setAdded(true)}>
|
|
91
|
+
{added ? "Added!" : "Add to Cart"}
|
|
92
|
+
</button>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Benefits for SPAs
|
|
98
|
+
|
|
99
|
+
### 1. Reduced Bundle Size
|
|
100
|
+
|
|
101
|
+
Unlike traditional SPAs where all code ships to the client, Server Components never reach the browser. Only static HTML (and glue code for client components) is sent.
|
|
102
|
+
|
|
103
|
+
### 2. Reduced Warm-Up Time
|
|
104
|
+
|
|
105
|
+
The browser does't need to execute Server Component code, leading to less JavaScript to parse and run on initial load. Only ship the code needed for interactivity.
|
|
106
|
+
|
|
107
|
+
### 3. Build-Time Data Fetching
|
|
108
|
+
|
|
109
|
+
Fetch data once at build time, embedded directly into your pre-rendered HTML:
|
|
110
|
+
|
|
111
|
+
```tsx
|
|
112
|
+
async function BlogIndex() {
|
|
113
|
+
// Fetched once during build, not on every page view
|
|
114
|
+
const posts = await fetchAllPosts();
|
|
115
|
+
return <PostList posts={posts} />;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 4. Full SPA Interactivity
|
|
120
|
+
|
|
121
|
+
On the browser, your app behaves like any SPA - client-side navigation, state management, and all the interactivity you need:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
// Client Component for interactive features
|
|
125
|
+
"use client";
|
|
126
|
+
|
|
127
|
+
import { useState } from "react";
|
|
128
|
+
import { useNavigate } from "@funstack/router";
|
|
129
|
+
|
|
130
|
+
export function SearchBox() {
|
|
131
|
+
const [query, setQuery] = useState("");
|
|
132
|
+
const navigate = useNavigate();
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<input
|
|
136
|
+
value={query}
|
|
137
|
+
onChange={(e) => setQuery(e.target.value)}
|
|
138
|
+
onKeyDown={(e) => {
|
|
139
|
+
if (e.key === "Enter") {
|
|
140
|
+
navigate(`/search?q=${query}`);
|
|
141
|
+
}
|
|
142
|
+
}}
|
|
143
|
+
/>
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### 5. Type-Safe Data Flow
|
|
149
|
+
|
|
150
|
+
Data flows from Server Components to Client Components with full TypeScript support:
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
interface Post {
|
|
154
|
+
id: string;
|
|
155
|
+
title: string;
|
|
156
|
+
content: string;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function BlogPost({ slug }: { slug: string }): Promise<JSX.Element> {
|
|
160
|
+
const post: Post = await fetchPost(slug);
|
|
161
|
+
return <Article post={post} />;
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Considerations
|
|
166
|
+
|
|
167
|
+
When using FUNSTACK Static, keep in mind:
|
|
168
|
+
|
|
169
|
+
1. **Build-time data** - Server Component data is fetched during build. For runtime data, use Client Components with standard fetch patterns.
|
|
170
|
+
2. **No server context** - No access to cookies, headers, or request data in Server Components.
|
|
171
|
+
3. **Only one entrypoint** - FUNSTACK Static apps are single-page applications, without any routing built in. All routing and navigation must be handled client-side.
|
|
172
|
+
|
|
173
|
+
This makes FUNSTACK Static ideal for developers looking to leverage RSC benefits while deploying simple static SPAs without server infrastructure.
|
|
174
|
+
|
|
175
|
+
## See Also
|
|
176
|
+
|
|
177
|
+
- [Getting Started](/funstack-static/getting-started) - Set up your first project
|
|
178
|
+
- [defer()](/funstack-static/api/defer) - Stream content progressively
|
|
179
|
+
- [funstackStatic()](/funstack-static/api/funstack-static) - Plugin configuration
|