The funstackStatic() function is the main Vite plugin that enables React Server Components for building SPAs without a runtime server.
import funstackStatic from "@funstack/static";
There are three configuration modes: single-entry (one HTML page), multiple entries (multiple HTML pages), and file-system routing (pages mapped from the file system).
Use root and app to produce a single index.html:
// vite.config.ts
import funstackStatic from "@funstack/static";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
funstackStatic({
root: "./src/root.tsx",
app: "./src/App.tsx",
}),
],
});
Use entries to produce multiple HTML pages from a single project:
// vite.config.ts
import funstackStatic from "@funstack/static";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
funstackStatic({
entries: "./src/entries.tsx",
}),
react(),
],
});
See Multiple Entrypoints for a full guide.
Experimental. Not yet covered by semantic versioning.
Use fsRoutes to map pages from a directory to routes, rendered with FUNSTACK Router:
// vite.config.ts
import funstackStatic from "@funstack/static";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
funstackStatic({
ssr: true,
fsRoutes: {
dir: "./src/pages",
root: "./src/root.tsx",
adapter: "@funstack/static/fs-routes/next-adapter",
},
}),
react(),
],
});
ssr is optional here, but enabling it (ssr: true) is recommended for SEO and faster initial load. See File-System Routing for a full guide.
The plugin accepts exactly one of root + app (single-entry), entries (multiple entries), or fsRoutes (file-system routing). These modes are mutually exclusive.
Type: string
Required in: single-entry mode
Path to the root component file. This component wraps your entire application and defines the HTML document structure (<html>, <head>, <body>).
Cannot be used together with entries.
funstackStatic({
root: "./src/root.tsx",
// ...
});
The root component receives children as a prop:
// src/root.tsx
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="UTF-8" />
<title>My Site</title>
</head>
<body>{children}</body>
</html>
);
}
Type: string
Required in: single-entry mode
Path to the app component file. This component defines your application's content.
Cannot be used together with entries.
funstackStatic({
root: "./src/root.tsx",
app: "./src/App.tsx",
});
// src/App.tsx
export default function App() {
return (
<div>
<h1>Welcome to My Site</h1>
{/* Your fantastic application goes here */}
</div>
);
}
Note: if your app has multiple pages, you can use a routing library here just like in a traditional SPA.
Type: string
Required in: multiple entries mode
Path to an entries module that exports a function returning entry definitions. Each entry produces its own HTML file.
Cannot be used together with root or app.
funstackStatic({
entries: "./src/entries.tsx",
});
The entries module must default-export a function that returns entry definitions:
// src/entries.tsx
import type { EntryDefinition } from "@funstack/static/entries";
export default function getEntries(): EntryDefinition[] {
return [
{
path: "index.html",
root: () => import("./root"),
app: () => import("./pages/Home"),
},
{
path: "about.html",
root: () => import("./root"),
app: () => import("./pages/About"),
},
];
}
See Multiple Entrypoints for details on the EntryDefinition type and advanced usage patterns like async generators.
Type: FsRoutesConfig
Required in: file-system routing mode
Experimental — not yet covered by semantic versioning.
Enables built-in file-system routing. Pages discovered under fsRoutes.dir are mapped to routes via an adapter and rendered with FUNSTACK Router. Requires @funstack/router to be installed.
Cannot be used together with root, app, or entries.
funstackStatic({
fsRoutes: {
dir: "./src/pages",
root: "./src/root.tsx",
adapter: "@funstack/static/fs-routes/next-adapter",
},
});
FsRoutesConfig fields (all required — you must be explicit about each):
dir (required) — directory scanned for route files, relative to the Vite root. Commonly "./src/pages".root (required) — path to the root (HTML shell) component module.adapter (required) — a module that export defaults an FsRoutesAdapter, given as a bare module specifier or a path relative to the Vite root. Use the bundled @funstack/static/fs-routes/next-adapter for the built-in Next.js-like convention, or point it at your own module for a custom one.Enabling ssr alongside fsRoutes is optional but recommended for SEO and faster initial load.
See File-System Routing for the conventions, dynamic routes, and writing custom adapters.
Type: string
Default: "dist/public"
Output directory for the generated static files.
funstackStatic({
root: "./src/root.tsx",
app: "./src/App.tsx",
publicOutDir: "build/static",
});
Type: boolean
Default: false
Enable server-side rendering of the App component.
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.
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.
funstackStatic({
root: "./src/root.tsx",
app: "./src/App.tsx",
ssr: true, // Enable full SSR
});
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.
Type: string
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.
The module is imported for its side effects only - no exports are needed.
funstackStatic({
root: "./src/root.tsx",
app: "./src/App.tsx",
clientInit: "./src/client-init.ts",
});
Example client init file:
// src/client-init.ts
import * as Sentry from "@sentry/browser";
Sentry.init({
dsn: "https://your-sentry-dsn",
environment: import.meta.env.MODE,
});
Note: Errors in the client init module will propagate normally and prevent the app from rendering.
Type: string
Path to a module that customizes the production build process. The module should export default a function that receives a BuildEntryContext with a build function (which performs the default build flow) and an outDir string (the output directory path).
This allows you to run additional work before, after, or in parallel with the build — for example, generating a sitemap or other static assets.
Only called during production builds (vite build), not in dev mode. The module runs in the RSC environment.
funstackStatic({
entries: "./src/entries.tsx",
build: "./src/build.ts",
});
See BuildEntryFunction for the full type definition and examples.
Type: string
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.
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.
funstackStatic({
root: "./src/root.tsx",
app: "./src/App.tsx",
rscPayloadDir: "my-custom-rsc-payload",
});
// vite.config.ts
import funstackStatic from "@funstack/static";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
funstackStatic({
root: "./src/root.tsx",
app: "./src/App.tsx",
publicOutDir: "dist/public",
}),
],
});
// vite.config.ts
import funstackStatic from "@funstack/static";
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [
funstackStatic({
entries: "./src/entries.tsx",
}),
react(),
],
});
You can use the same Vite commands you would use in a normal Vite project:
vite dev - Starts the development servervite build - Builds the static filesvite preview - Previews the built static files locally