Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/docs/src/pages/advanced/MultipleEntrypoints.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ dist/public/
├── blog/
│ └── post-1.html
├── funstack__/
│ └── fun:rsc-payload/
│ └── fun__rsc-payload/
│ ├── a1b2c3d4.txt # RSC payload for index.html
│ ├── e5f6g7h8.txt # RSC payload for about.html
│ ├── i9j0k1l2.txt # RSC payload for blog/post-1.html
Expand Down
6 changes: 3 additions & 3 deletions packages/docs/src/pages/api/Defer.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ interface DeferOptions {
```

- **name:** An optional identifier to help with debugging. When provided:
- In development mode, the name is included in the RSC payload file name (e.g., `/funstack__/fun:rsc-payload/HomePage-b5698be72eea3c37`)
- In development mode, the name is included in the RSC payload file name (e.g., `/funstack__/fun__rsc-payload/HomePage-b5698be72eea3c37`)
- In production mode, the name is logged when the payload file is emitted

### Returns
Expand Down Expand Up @@ -105,6 +105,6 @@ Using the `name` option makes it easier to identify which deferred component cor

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.

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`).
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`).

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.
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.
8 changes: 4 additions & 4 deletions packages/docs/src/pages/api/FunstackStatic.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -229,19 +229,19 @@ Sentry.init({
### rscPayloadDir (optional)

**Type:** `string`
**Default:** `"fun:rsc-payload"`
**Default:** `"fun__rsc-payload"`

Directory name used for RSC payload files in the build output. The final file paths follow the pattern `/funstack__/{rscPayloadDir}/{hash}.txt`.

Change this if your hosting platform has issues with the default directory name. For example, Cloudflare Workers redirects URLs containing colons to percent-encoded equivalents, adding an extra round trip.
Change this if your hosting platform has issues with the default directory name.

**Important:** The value is used as a marker for string replacement during the build process. Choose a value that is unique enough that it does not appear in your application's source code. The default value `"fun:rsc-payload"` is designed to be unlikely to collide with user code.
**Important:** The value is used as a marker for string replacement during the build process. Choose a value that is unique enough that it does not appear in your application's source code. The default value `"fun__rsc-payload"` is designed to be unlikely to collide with user code.

```typescript
funstackStatic({
root: "./src/root.tsx",
app: "./src/App.tsx",
rscPayloadDir: "fun-rsc-payload", // Avoid colons for Cloudflare Workers
rscPayloadDir: "my-custom-rsc-payload",
});
```

Expand Down
4 changes: 2 additions & 2 deletions packages/docs/src/pages/learn/HowItWorks.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,12 @@ dist/public
│ ├── root-DvE5ENz2.css
│ └── rsc-D0fjt5Ie.js
├── funstack__
│ └── fun:rsc-payload
│ └── fun__rsc-payload
│ └── db1923b9b6507ab4.txt
└── index.html
```

The RSC payload files under `funstack__` are loaded by the client-side code to bootstrap the application with server-rendered content. The `fun:rsc-payload` directory name is [configurable](/api/funstack-static#rscpayloaddir-optional) via the `rscPayloadDir` option.
The RSC payload files under `funstack__` are loaded by the client-side code to bootstrap the application with server-rendered content. The `fun__rsc-payload` directory name is [configurable](/api/funstack-static#rscpayloaddir-optional) via the `rscPayloadDir` option.

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).

Expand Down
4 changes: 2 additions & 2 deletions packages/docs/src/pages/learn/OptimizingPayloads.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Without any optimization, your entire application is rendered into one RSC paylo

```
dist/public/funstack__/
└── fun:rsc-payload/
└── fun__rsc-payload/
└── b62ec6668fd49300.txt ← Contains everything
```

Expand Down Expand Up @@ -75,7 +75,7 @@ After building with route-level `defer()`, your output looks like this:

```
dist/public/funstack__/
└── fun:rsc-payload/
└── fun__rsc-payload/
├── a3f2b1c9d8e7f6a5.txt ← Home page
├── b5698be72eea3c37.txt ← About page
├── b62ec6668fd49300.txt ← Main app shell
Expand Down
2 changes: 1 addition & 1 deletion packages/static/design/multiple-entries.md
Original file line number Diff line number Diff line change
Expand Up @@ -477,7 +477,7 @@ dist/public/
├── blog/
│ └── post-1.html # path: "blog/post-1.html"
├── funstack__/
│ └── fun:rsc-payload/
│ └── fun__rsc-payload/
│ ├── a1b2c3d4e5f6g7h8.txt # RSC payload for index.html
│ ├── i9j0k1l2m3n4o5p6.txt # RSC payload for about.html
│ ├── q7r8s9t0u1v2w3x4.txt # RSC payload for blog/post-1.html
Expand Down
4 changes: 2 additions & 2 deletions packages/static/e2e/tests/build.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ test.describe("Build output verification", () => {
expect(html).toContain("__FUNSTACK_APP_ENTRY__");
// Verify the RSC payload is preloaded
expect(html).toContain('rel="preload"');
expect(html).toContain("funstack__/fun:rsc-payload/");
expect(html).toContain("funstack__/fun__rsc-payload/");
});

test("generates RSC payload files at /funstack__/*.txt", async ({
Expand All @@ -32,7 +32,7 @@ test.describe("Build output verification", () => {

// Look for the RSC payload in preload link or FUNSTACK config
const rscPayloadMatch = html.match(
/funstack__\/fun:rsc-payload\/[^"'\s]+\.txt/,
/funstack__\/fun__rsc-payload\/[^"'\s]+\.txt/,
);
expect(rscPayloadMatch).not.toBeNull();

Expand Down
5 changes: 4 additions & 1 deletion packages/static/e2e/tests/hydration.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,10 @@ test.describe("Client-side hydration", () => {

page.on("request", (request) => {
const url = request.url();
if (url.includes("funstack__/fun:rsc-payload/") && url.endsWith(".txt")) {
if (
url.includes("funstack__/fun__rsc-payload/") &&
url.endsWith(".txt")
) {
rscRequests.push(url);
}
});
Expand Down
8 changes: 4 additions & 4 deletions packages/static/e2e/tests/multi-entry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ test.describe("Multi-entry build output", () => {
expect(html).toContain("<!DOCTYPE html>");
expect(html).toContain("<html");
expect(html).toContain("__FUNSTACK_APP_ENTRY__");
expect(html).toContain("funstack__/fun:rsc-payload/");
expect(html).toContain("funstack__/fun__rsc-payload/");
});

test("generates about.html with expected HTML structure", async ({
Expand All @@ -24,7 +24,7 @@ test.describe("Multi-entry build output", () => {
expect(html).toContain("<!DOCTYPE html>");
expect(html).toContain("<html");
expect(html).toContain("__FUNSTACK_APP_ENTRY__");
expect(html).toContain("funstack__/fun:rsc-payload/");
expect(html).toContain("funstack__/fun__rsc-payload/");
});

test("each page has its own RSC payload", async ({ request }) => {
Expand All @@ -36,10 +36,10 @@ test.describe("Multi-entry build output", () => {

// Both pages should reference RSC payloads
const homePayloadMatch = homeHtml.match(
/funstack__\/fun:rsc-payload\/[^"'\s]+\.txt/,
/funstack__\/fun__rsc-payload\/[^"'\s]+\.txt/,
);
const aboutPayloadMatch = aboutHtml.match(
/funstack__\/fun:rsc-payload\/[^"'\s]+\.txt/,
/funstack__\/fun__rsc-payload\/[^"'\s]+\.txt/,
);

expect(homePayloadMatch).not.toBeNull();
Expand Down
8 changes: 4 additions & 4 deletions packages/static/src/build/dependencyGraph.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,15 @@ describe("findReferencedIds", () => {
});

it("finds IDs with special characters", () => {
const content = "Reference to fun:rsc-payload/abc-123 here";
const content = "Reference to fun__rsc-payload/abc-123 here";
const allKnownIds = new Set([
"fun:rsc-payload/abc-123",
"fun:rsc-payload/def-456",
"fun__rsc-payload/abc-123",
"fun__rsc-payload/def-456",
]);

const result = findReferencedIds(content, allKnownIds);

expect(result).toEqual(new Set(["fun:rsc-payload/abc-123"]));
expect(result).toEqual(new Set(["fun__rsc-payload/abc-123"]));
});
});

Expand Down
2 changes: 1 addition & 1 deletion packages/static/src/build/rscProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ interface RawComponent {
*
* @param deferRegistryIterator - Iterator yielding components with { id, data }
* @param appRscStream - The main RSC stream
* @param rscPayloadDir - Directory name used as a prefix for RSC payload IDs (e.g. "fun:rsc-payload")
* @param rscPayloadDir - Directory name used as a prefix for RSC payload IDs (e.g. "fun__rsc-payload")
* @param context - Optional context for logging warnings
*/
export async function processRscComponents(
Expand Down
4 changes: 2 additions & 2 deletions packages/static/src/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ interface FunstackStaticBaseOptions {
* The final path will be `/funstack__/{rscPayloadDir}/{hash}.txt`.
*
* Change this if your hosting platform has issues with the default
* directory name (e.g. Cloudflare Workers redirects URLs containing colons).
* directory name.
*
* The value is used as a marker for string replacement during the build
* process, so it should be unique enough that it does not appear in your
* application's source code.
*
* @default "fun:rsc-payload"
* @default "fun__rsc-payload"
*/
rscPayloadDir?: string;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/static/src/rsc/rscModule.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
/**
* Default directory name for RSC payload files.
*/
export const defaultRscPayloadDir = "fun:rsc-payload";
export const defaultRscPayloadDir = "fun__rsc-payload";

/**
* Combines the RSC payload directory with a raw ID to form a
* namespaced payload ID (e.g. "fun:rsc-payload/abc123").
* namespaced payload ID (e.g. "fun__rsc-payload/abc123").
*/
export function getPayloadIDFor(
rawId: string,
Expand Down