---
title: renderToStatic
---

# renderToStatic

Builds HTML files at compile time: renders your pages, then emits a fragment
file per lazy `Defer`.

## Signature

```tsx
import { renderToStatic } from "@vincle/flow";

// Pure static: no adapter, no emitFragments
function renderToStatic<T>(fn: (ctx: PureStaticContext) => T): Promise<T>;

// With deferred fragments: adapter required
function renderToStatic<T>(fn: (ctx: StaticContext) => T, options: StaticOptions): Promise<T>;
```

## Options

| Option         | Type                     | Default                              | Description                              |
| -------------- | ------------------------ | ------------------------------------ | ---------------------------------------- |
| `adapter`      | `Adapter`                | Required                             | Wire-format adapter for fragment framing |
| `generatePath` | `(id: string) => string` | ``(id) => `/fragments/${id}.html` `` | URL path for each fragment               |

Options are validated at the call, before anything renders: see
[Error handling](/api/core/error-handling).

## Contexts

| Method          | Signature                                                                                   | Description                                              |
| --------------- | ------------------------------------------------------------------------------------------- | -------------------------------------------------------- |
| `renderPage`    | `(node: () => JSX.Element) => Promise<string>`                                              | Renders a page returning the full HTML string            |
| `emitFragments` | `(emit: (id: string, url: string, html: string) => void \| Promise<void>) => Promise<void>` | Materializes deferred fragments, passes each to callback |

Called without options, the handler receives a `PureStaticContext`: it has no
`emitFragments`, so the type system prevents calling it. With `{ adapter }`, it
receives a `StaticContext`, which has both.

## Usage

```tsx
import type { JSX } from "@vincle/core";

import { renderToStatic } from "@vincle/flow";
import { NativeAdapter } from "@vincle/flow/adapters";
import { writeFile } from "node:fs/promises";

declare const pages: { Component: () => JSX.Element; out: string }[];

await renderToStatic(
  async (ctx) => {
    for (const page of pages) {
      const html = await ctx.renderPage(() => <page.Component />);
      await writeFile(page.out, "<!DOCTYPE html>\n" + html);
    }

    // One .html file per deferred fragment, already Frame-wrapped.
    await ctx.emitFragments((_id, url, html) => writeFile("./dist" + url, html));
  },
  { adapter: NativeAdapter },
);
```

Each placeholder's `src` defaults to `generatePath(id)`, the same path, so the
browser finds each fragment.
