FFUNSTACK Static
DocsAPILearn

Getting Started

IntroductionMigrating from Vite SPA

Learn

React Server ComponentsHow It WorksOptimizing RSC PayloadsUsing lazy() in ServerPrefetching with ActivityFile-System Routing

Advanced

Multiple Entrypoints (SSG)Server-Side Rendering

API Reference

funstackStatic()defer()EntryDefinition

Help

FAQ

funstackStatic()

The funstackStatic() function is the main Vite plugin that enables React Server Components for building SPAs without a runtime server.

Import

import funstackStatic from "@funstack/static";

Usage

There are two configuration modes: single-entry (one HTML page) and multiple entries (multiple HTML pages).

Single-Entry Mode

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",
    }),
  ],
});

Multiple Entries Mode

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.

Options

The plugin accepts either root + app (single-entry) or entries (multiple entries). These two modes are mutually exclusive.

root

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>
  );
}

app

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.

entries

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.

publicOutDir (optional)

Type: string Default: "dist/public"

Output directory for the generated static files.

funstackStatic({
  root: "./src/root.tsx",
  app: "./src/App.tsx",
  publicOutDir: "build/static",
});

ssr (optional)

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.

clientInit (optional)

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.

rscPayloadDir (optional)

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",
});

Full Example

Single-Entry

// 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",
    }),
  ],
});

Multiple Entries

// 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(),
  ],
});

Vite Commands

You can use the same Vite commands you would use in a normal Vite project:

  • vite dev - Starts the development server
  • vite build - Builds the static files
  • vite preview - Previews the built static files locally

See Also

  • Getting Started - Quick start guide
  • Multiple Entrypoints - Multi-page static site generation
  • defer() - Deferred rendering for streaming
  • React Server Components - Understanding RSC