---
title: "Renderers"
description: "Choose React, Preact, Solid, Vue, or Svelte for application UI while keeping FARMJS routing, server APIs, middleware, observability, and deployment."
canonical_url: "https://mcp.farmjs.dev/docs/renderers"
markdown_url: "https://mcp.farmjs.dev/docs/renderers.md"
last_updated: "2026-10-20"
x_farming_labs_generated_preamble: true
---

# Renderers
URL: /docs/renderers
LLM index: /llms.txt
Description: Choose React, Preact, Solid, Vue, or Svelte for application UI while keeping FARMJS routing, server APIs, middleware, observability, and deployment.

# Renderers

Choose React, Preact, Solid, Vue, or Svelte for application UI while keeping FARMJS routing, server
APIs, middleware, observability, and deployment. React remains the default; Preact, Solid, Vue, and
Svelte are beta renderer adapters.

## Choose a renderer

| Renderer                         | Select it with       | Route components | Best fit                                                     |
| -------------------------------- | -------------------- | ---------------- | ------------------------------------------------------------ |
| [React](/docs/renderers/react)   | Omit `renderer`      | `.tsx`, `.jsx`   | Complete FARMJS client API and integration UI support.       |
| [Preact](/docs/renderers/preact) | `renderer: preact()` | `.tsx`, `.jsx`   | Small React-compatible runtime with streaming SSR.           |
| [Solid](/docs/renderers/solid)   | `renderer: solid()`  | `.tsx`, `.jsx`   | Fine-grained interactive UI with FARMJS server features.     |
| [Vue](/docs/renderers/vue)       | `renderer: vue()`    | `.vue`           | Vue SFCs, SSR, hydration, and FARMJS server features.        |
| [Svelte](/docs/renderers/svelte) | `renderer: svelte()` | `.svelte`        | Svelte 5 components, runes, SSR, and FARMJS server features. |

The renderer controls component compilation, element creation, server rendering, and browser
hydration. FARMJS continues to control route discovery, layouts, API routes, middleware, cache and
storage, integrations, observability, and deployment output.

## Feature support

The first two tables are checked against the renderer descriptors and their server/client exports,
so entry-point or capability changes cannot silently leave the documentation stale.

<!-- renderer-capability-matrix:start -->

| Renderer | Vite entry                          | Server entry                          | Client entry                          | Route module extensions                 |
| -------- | ----------------------------------- | ------------------------------------- | ------------------------------------- | --------------------------------------- |
| React    | `@farm.js/core/renderer/react/vite` | `@farm.js/core/renderer/react/server` | `@farm.js/core/renderer/react/client` | `.ts`, `.tsx`, `.js`, `.jsx`            |
| Preact   | `@farm.js/preact/vite`              | `@farm.js/preact/server`              | `@farm.js/preact/client`              | `.ts`, `.tsx`, `.js`, `.jsx`            |
| Solid    | `@farm.js/solid/vite`               | `@farm.js/solid/server`               | `@farm.js/solid/client`               | `.ts`, `.tsx`, `.js`, `.jsx`            |
| Vue      | `@farm.js/vue/vite`                 | `@farm.js/vue/server`                 | `@farm.js/vue/client`                 | `.ts`, `.tsx`, `.js`, `.jsx`, `.vue`    |
| Svelte   | `@farm.js/svelte/vite`              | `@farm.js/svelte/server`              | `@farm.js/svelte/client`              | `.ts`, `.tsx`, `.js`, `.jsx`, `.svelte` |

| Renderer | SSR | Streaming                                                                                                                | Hydration | Component head output | Route updates                         | Plain functions |
| -------- | --- | ------------------------------------------------------------------------------------------------------------------------ | --------- | --------------------- | ------------------------------------- | --------------- |
| React    | Yes | Node (Web on edge targets)                                                                                               | Yes       | Framework metadata    | Reconciles                            | Yes             |
| Preact   | Yes | Node + Web                                                                                                               | Yes       | Framework metadata    | Reconciles                            | Yes             |
| Solid    | Yes | Node + Web                                                                                                               | Yes       | Framework metadata    | Remounts ([why](#re-render-behavior)) | Yes             |
| Vue      | Yes | Node + Web                                                                                                               | Yes       | Framework metadata    | Reconciles                            | Yes             |
| Svelte   | Yes | Buffered ([test](https://github.com/farming-labs/farm.js/blob/main/packages/farm-svelte/src/__tests__/renderer.test.ts)) | Yes       | Native + framework    | Reconciles                            | Yes             |

<!-- renderer-capability-matrix:end -->

“Component head output” means markup emitted from inside the renderer's component model. All five
renderers support FARMJS static metadata; Svelte additionally carries `<svelte:head>` output through
its server adapter. “Route updates” describes what happens when FARMJS hands an existing browser
root a freshly materialized route tree, not ordinary reactive updates inside a mounted component.

The higher-level framework features below use those adapter primitives:

| Capability                                       | React                    | Preact                          | Solid                | Vue                  | Svelte               |
| ------------------------------------------------ | ------------------------ | ------------------------------- | -------------------- | -------------------- | -------------------- |
| File pages and nested layouts                    | Available                | Available                       | Available            | Available            | Available            |
| Loading, error, not-found, and slot files        | Available                | Available                       | Available            | Available            | Available            |
| Static metadata and favicon configuration        | Available                | Available                       | Available            | Available            | Available            |
| API routes and generated typed API clients       | Available                | Available                       | Available            | Available            | Available            |
| Server functions, middleware, cache, and storage | Available                | Available                       | Available            | Available            | Available            |
| Observability and production Node output         | Available                | Available                       | Available            | Available            | Available            |
| Basic create-app starter                         | Available                | Available                       | Available            | Available            | Available            |
| Better Auth create-app starter                   | Available                | Native                          | Native               | Native               | Native               |
| Router state and programmatic navigation         | Available                | Through `preact/compat`         | Solid binding        | Vue binding          | Svelte store         |
| Callable actions and server queries              | Available                | Through `preact/compat`         | Solid binding        | Vue binding          | Svelte store         |
| Client theme and i18n state                      | Available                | Through `preact/compat`         | Solid binding        | Vue binding          | Svelte store         |
| Renderer-specific `Link` and form components     | Available                | Through `preact/compat`         | Not yet              | Not yet              | Not yet              |
| Programmatic UI routes                           | Available                | Compatibility surface           | React-oriented today | React-oriented today | React-oriented today |
| Markdown/MDX visual routes and docs adapter      | Available                | Compatibility surface           | React-oriented today | React-oriented today | React-oriented today |
| Generated JSX metadata images                    | Available                | Compatibility surface           | React-oriented today | React-oriented today | React-oriented today |
| React Server Components and optimized boundaries | Available experimentally | Not applicable                  | Not applicable       | Not applicable       | Not applicable       |
| Other integration UI providers and starters      | Available                | Provider-specific compatibility | React-oriented today | React-oriented today | React-oriented today |

Follow the focused compatibility notes for [Preact](/docs/renderers/preact#react-compatibility),
[Solid](/docs/renderers/solid#current-boundaries), [Vue](/docs/renderers/vue#current-boundaries), and
[Svelte](/docs/renderers/svelte#current-boundaries) before choosing a non-React renderer for a
React-oriented UI surface.

In experimental React Server Components, synchronous page and layout components are rendered through React, including supported server hooks such as `useId()`. A string or variable containing `async` does not make a component asynchronous. Stateful hooks and effects still belong in Client Components.

Preact resolves the React-shaped bindings through `preact/compat`. Solid exposes signal-backed
getters, Vue exposes refs and computed values, and Svelte exposes readable stores. The underlying
navigation, action, query-cache, theme, and i18n transports live in the renderer-neutral
`@farm.js/core/renderer-client` entry.

## Native client bindings

Import client bindings from the selected renderer rather than importing React hooks:

```ts
// Solid
import { useAction, useRouter, useServerQuery, useTheme } from "@farm.js/solid/bindings";

// Vue
import { useAction, useRouter, useServerQuery, useTheme } from "@farm.js/vue/bindings";

// Svelte
import {
  createAction,
  createRouter,
  createServerQuery,
  createTheme,
} from "@farm.js/svelte/bindings";
```

Actions remain normal typed RPC calls. Solid exposes action state through reactive properties, Vue
through refs, and Svelte through the callable action's readable-store subscription. Server queries
share FARMJS's existing browser cache, invalidation, deduplication, stale-while-revalidate, focus,
and reconnect behavior across all bindings.

## Renderer capability contract

Renderer packages advertise streaming support in their descriptor instead of relying on FARMJS to
guess from optional runtime exports:

```ts
import { defineRenderer } from "@farm.js/core";

export const customRenderer = defineRenderer({
  name: "custom",
  vite: "@example/renderer/vite",
  server: "@example/renderer/server",
  client: "@example/renderer/client",
  capabilities: {
    streaming: {
      node: true,
      web: false,
      runtimes: {
        edge: { node: false, web: true },
      },
    },
    reconcilesRerenders: false,
    functionComponents: false,
  },
});
```

The top-level streaming values are the fallback when no runtime override exists and for presets
whose runtime is unknown. Local development resolves as `node`; production resolves from the
deployment preset. A `runtimes.node` or `runtimes.edge` entry overrides the pair once FARMJS knows
the runtime. Existing renderers that support the same primitives everywhere can keep using only
the two booleans.

| Renderer | Node target        | Edge target        |
| -------- | ------------------ | ------------------ |
| React    | Node stream        | Web stream         |
| Preact   | Node + Web streams | Node + Web streams |
| Solid    | Node + Web streams | Node + Web streams |
| Vue      | Node + Web streams | Node + Web streams |
| Svelte   | Buffered           | Buffered           |

### Re-render behavior

`reconcilesRerenders` states whether re-rendering an existing root diffs the new tree against the
live DOM or rebuilds it.

Virtual-DOM renderers (React, Preact, Vue) compare the incoming tree with what is mounted. Svelte's
FARMJS compatibility root likewise applies a new element description through one mounted reactive
root. In both cases, a client navigation keeps matching DOM nodes, focus, and component state.

Solid remains a compile-time fine-grained renderer: handing `root.render()` a freshly materialized
tree replaces its nodes because there is no virtual DOM to diff. FARMJS therefore does not use
ordinary root re-rendering for a shared Solid layout. The client runtime supplies route state to the
adapter's optional `renderRoute()` method instead; the Solid adapter keeps the matching layout chain
mounted and updates its page slot and params through signals. Changing the layout chain still mounts
the new chain, as it does in the other renderers.

Renderers that do not declare the field are treated as rebuilding, so nothing silently depends on
reconciliation it will not get. The shared renderer conformance suite asserts the behavior each
renderer declares, so the flag cannot drift away from what the adapter actually does.

Custom fine-grained renderers can implement the same optional route-update contract:

```ts
import type { FarmRendererClientRoot, FarmRendererRouteState } from "@farm.js/core/renderer";

interface CustomRoot extends FarmRendererClientRoot {
  renderRoute(state: FarmRendererRouteState): void;
}

export function hydrateRoot(
  container: Element,
  element: unknown,
  initialRoute?: FarmRendererRouteState,
): CustomRoot {
  // Establish native reactive bindings from initialRoute during hydration.
  // Later navigations call root.renderRoute(nextRoute).
}
```

`layouts` arrive outermost first with stable route patterns, `page` is the innermost route slot, and
`params` is the current route-param snapshot. `element` is the fully composed fallback tree, while
`wrap()` reapplies framework-owned outer wrappers such as integration providers. Renderers that omit
`renderRoute()` continue through `render(element)` unchanged.

### Function components

`functionComponents` states whether the renderer can render a plain function component: one that
takes props and returns an element tree rather than a component built by the renderer's own
compiler. FARMJS gates integration provider components on this capability.

All official renderers support synchronous plain function components. React and Preact handle them
through their native element model, while Vue and Solid materialize their returned element tree in
the adapter. Vue and Svelte also need their adapters to distinguish a plain function from one of
their own components, because both are functions at runtime: a native Vue functional component
takes `(props, { slots, attrs, emit })` and returns VNodes, and a compiled Svelte component is a
function too. Their adapters call only the functions FARMJS marked and render every other function
as a native component, so Vue functional components keep their slots, attrs, and emit.

FARMJS resolves the ambiguity at build time rather than guessing. A provider component in a
production build must be an importable module reference, so the module's extension answers the
question: a `.svelte` provider under the Svelte renderer is a Svelte component, while a `.tsx` one is
a function component. The check uses the extensions the renderer itself declares in
`componentExtensions`, not the resolved set, which always includes `.ts`, `.tsx`, `.js`, and `.jsx`.
Components that are not renderer-compiled are marked so the adapter calls them instead of
instantiating them.

Async plain function components are not part of this contract. Official adapters reject them with
an actionable error instead of rendering a promise, an empty string, or `[object Object]`. Resolve
the data before creating the element tree or use the renderer's native asynchronous primitives.

The field defaults to `false`, so a renderer whose adapter has not been taught to handle function
components fails with a clear error naming the renderer rather than rendering something broken.

FARMJS builds the production client and SSR graphs in parallel by default. If a renderer's compiler
plugin uses process-global mutable caches, set `buildConcurrency: "serial"` on its descriptor. The
official Vue renderer does this because `@vitejs/plugin-vue` shares SFC descriptor and script caches
between plugin instances.

A renderer advertising `node` streaming for the active runtime must export
`renderToPipeableStream()` from its server entry. A renderer advertising `web` streaming must
export `renderToReadableStream()` returning a WHATWG `ReadableStream`. FARMJS validates those
declarations when the development server renderer starts and again in the generated production
runtime. Production still checks the function before calling it, but only a primitive enabled by
the resolved descriptor can be selected. This keeps the declaration and runtime export in
agreement instead of treating an accidental export as support. FARMJS uses buffered
`renderToString()` when neither capability is enabled. Descriptors without a `capabilities` field
remain buffered for compatibility.

## Renderer-neutral server code

Keep product and server behavior outside the component runtime whenever possible:

```ts
import { createEndpoint } from "@farm.js/core";
import { createServerFn } from "@farm.js/core/server-fn";
import { z } from "zod";

const input = z.object({ name: z.string().min(1) });

const greet = createServerFn({
  input,
  async handler({ input }) {
    return { message: `Hello, ${input.name}` };
  },
});

export const POST = createEndpoint(
  "/api/greeting",
  { method: "POST", body: input },
  async ({ body }) => greet(body),
);
```

React, Preact, Solid, Vue, and Svelte components can call this endpoint through the same generated client.
Database access, secrets, validation, cache invalidation, middleware, and the server-function
handler remain on the server.

## Switch renderers deliberately

The renderer option is application-wide. Do not mix React, Preact, Solid, Vue, and Svelte route components
in the same route tree. Share server modules, schemas, API clients, CSS, and plain TypeScript across
renderers; rewrite component and client-state code using the selected renderer's native primitives.

The Basic and Better Auth templates support every renderer directly:

```bash
pnpm create @farm.js/app my-auth-app --template better-auth --renderer vue --typescript
```

Other integration starter templates currently target React. Add their renderer-neutral server
integration to a native Basic starter when using Preact, Solid, Vue, or Svelte.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
