Farm.js

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

RendererSelect it withRoute componentsBest fit
ReactOmit renderer.tsx, .jsxComplete FARMJS client API and integration UI support.
Preactrenderer: preact().tsx, .jsxSmall React-compatible runtime with streaming SSR.
Solidrenderer: solid().tsx, .jsxFine-grained interactive UI with FARMJS server features.
Vuerenderer: vue().vueVue SFCs, SSR, hydration, and FARMJS server features.
Svelterenderer: svelte().svelteSvelte 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.

RendererVite entryServer entryClient entryRoute 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
RendererSSRStreamingHydrationComponent head outputRoute updatesPlain functions
ReactYesNode (Web on edge targets)YesFramework metadataReconcilesYes
PreactYesNode + WebYesFramework metadataReconcilesYes
SolidYesNode + WebYesFramework metadataRemounts (why)Yes
VueYesNode + WebYesFramework metadataReconcilesYes
SvelteYesBuffered (test)YesNative + frameworkReconcilesYes

“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:

CapabilityReactPreactSolidVueSvelte
File pages and nested layoutsAvailableAvailableAvailableAvailableAvailable
Loading, error, not-found, and slot filesAvailableAvailableAvailableAvailableAvailable
Static metadata and favicon configurationAvailableAvailableAvailableAvailableAvailable
API routes and generated typed API clientsAvailableAvailableAvailableAvailableAvailable
Server functions, middleware, cache, and storageAvailableAvailableAvailableAvailableAvailable
Observability and production Node outputAvailableAvailableAvailableAvailableAvailable
Basic create-app starterAvailableAvailableAvailableAvailableAvailable
Better Auth create-app starterAvailableNativeNativeNativeNative
Router state and programmatic navigationAvailableThrough preact/compatSolid bindingVue bindingSvelte store
Callable actions and server queriesAvailableThrough preact/compatSolid bindingVue bindingSvelte store
Client theme and i18n stateAvailableThrough preact/compatSolid bindingVue bindingSvelte store
Renderer-specific Link and form componentsAvailableThrough preact/compatNot yetNot yetNot yet
Programmatic UI routesAvailableCompatibility surfaceReact-oriented todayReact-oriented todayReact-oriented today
Markdown/MDX visual routes and docs adapterAvailableCompatibility surfaceReact-oriented todayReact-oriented todayReact-oriented today
Generated JSX metadata imagesAvailableCompatibility surfaceReact-oriented todayReact-oriented todayReact-oriented today
React Server Components and optimized boundariesAvailable experimentallyNot applicableNot applicableNot applicableNot applicable
Other integration UI providers and startersAvailableProvider-specific compatibilityReact-oriented todayReact-oriented todayReact-oriented today

Follow the focused compatibility notes for Preact, Solid, Vue, and Svelte 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:

// 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:

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.

RendererNode targetEdge target
ReactNode streamWeb stream
PreactNode + Web streamsNode + Web streams
SolidNode + Web streamsNode + Web streams
VueNode + Web streamsNode + Web streams
SvelteBufferedBuffered

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:

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. A compile-time renderer such as Svelte also needs its adapter to distinguish a plain function from one of its own compiled components, because both are functions at runtime.

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:

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:

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.