Docs

Frameworks

Nifra renders five UI frameworks on one agnostic core. The render seam, file-based routes, typed loaders/actions, streaming, islands, prefetch, and the client router are shared across adapters, while each page still uses its framework's normal component style.

Scaffold any of them

create-nifra's --framework flag scaffolds the multi-target SSR site with the adapter, routes, build wiring, and deps for your pick. It composes with --deploy, so one command gives you a framework + a default deploy target.

TS
# Scaffold a multi-target SSR site in any of the five (react is the default):
bun create nifra my-app --framework solid      # or react · preact · vue · svelte

# Composes with the deploy preset - pick a framework AND a default deploy target:
bun create nifra my-app --framework svelte --deploy vercel

The adapters

FrameworkPackageUI idiomBuild pluginHydration bundle
None (vanilla)@nifrajs/web-vanillatagged-template HTML + islandsnone0 KB
Solid@nifrajs/web-solidprimitivesBabel~15 KB
Preact@nifrajs/web-preacthooks (React-compat)none~18 KB
Svelte 5@nifrajs/web-sveltestores + runes.svelte compiler~49 KB
Vue 3@nifrajs/web-vuecomposables.vue SFC (or render fns)~66 KB
React 19@nifrajs/web-reacthooksnone (Bun JSX)~182 KB
Bundle = the same minimal counter app, minified (not gzipped), for each framework - see examples/web-*. Indicative payload, not a benchmark.

Or no framework at all

@nifrajs/web-vanilla is the first row, and it is not a sixth framework - it is the absence of one. Pages are plain functions returning an auto-escaping html tagged template, and the client ships no framework runtime, so the bundle is genuinely zero rather than small.

TS
import { html, vanillaAdapter } from "@nifrajs/web-vanilla"

// routes/hotels.ts - a route file, no .tsx needed.
export const hydrate = false

export default function Hotels({ data }: { data: { hotels: Array<{ name: string }> } }) {
  // Interpolated values are escaped; wrap trusted markup in raw() to opt out deliberately.
  return html`<ul>${data.hotels.map((h) => html`<li>${h.name}</li>`)}</ul>`
}

Everything that lives in @nifrajs/web rather than the view layer works unchanged: loaders, actions, ISR, SSG, head management, streaming. What you give up is hydration - these are server-rendered documents, so set export const hydrate = false and reach for islands where a page needs interactivity.

Worth it for the surfaces where HTML is the product: landing pages, listings, docs, comparison tables. This documentation site renders that way.

Authoring routes

A route's default export is the component; its loader/action/meta are named exports. Most adapters write .tsx, but the compiled frameworks use their native single-file format - Svelte .svelte (loader/meta in <script module>) and Vue .vue SFCs (loader/meta in the plain <script>; the component in <script setup> + <template>) - each compiled by its package's Bun plugin (@nifrajs/web-vue/plugin, @nifrajs/web-svelte/plugin).

TS
<!-- routes/index.vue - a Nifra route authored as a Vue Single-File Component -->
<script lang="ts">
// The plain <script> carries Nifra's route convention (server-only named exports):
export const meta = { title: "Home" }
export async function loader({ api }) {
  const res = await api.count.get()
  return { count: res.data?.count ?? 0 }
}
</script>

<script setup lang="ts">
defineProps(["data"])          // Nifra passes the loader data in as `data`
</script>

<template>
  <h1>Count: {{ data.count }}</h1>
</template>
Component <style scoped> (Vue) and <style> (Svelte - scoped by default) are compiled and scoped into the app stylesheet - no runtime, no FOUC - plus CSS Modules and global imports. See Dev & HMR → Styling. examples/routing-vue-sfc is a full SSR + hydration + client-nav demo.

Same features, every framework

Every adapter gets Nifra's full feature set - not a subset:

  • Streaming SSR + hydration (islands), with component-level Suspense.
  • File-based routing, nested layouts, typed loader/action, and progressive-enhancement forms (work with JS off).
  • <Await> for deferred data (defer()), a keyed query cache (useQuery), and concurrent useFetcher mutations.
  • Optimistic UI, targeted revalidation, hover-prefetch, and scroll restoration.
  • True HMR in dev for all five (@nifrajs/web/vite) - see Dev & HMR.

One line changes

The adapter is the only framework-specific choice on the server. Swap it and the entire app - routes, data, streaming - runs on a different framework, unchanged.

TS
// Server - pick an adapter. Everything else is identical across all five frameworks:
// the same routes, loaders, actions, streaming, <Await>, fetchers, query cache.
import { createWebApp } from "@nifrajs/web"
import { reactAdapter } from "@nifrajs/web-react"   // ← or web-solid · web-vue · web-preact · web-svelte
import { clientEntry, manifest } from "./server-manifest" // generated by buildServer

const app = createWebApp({
  adapter: reactAdapter,   // the one line that changes per framework
  manifest,
  clientEntry,             // the built client bundle (from buildClient's manifest)
})

Idiomatic, not lowest-common-denominator

The bindings share names and behaviour across frameworks, but each is expressed the native way - React/Preact hooks, Vue composables, Solid signals, Svelte stores.

TS
// The data primitives - same names + behaviour everywhere, each in the framework's idiom.
// React / Preact - hooks:
const fetcher = useFetcher("save")          // fetcher.submit(...), fetcher.pending
const q = useQuery(["todos"], loadTodos)    // q.data, q.isFetching, q.refetch()

// Vue - composables (refs):           const f = useFetcher("save"); f.state.value.pending
// Solid - signals:                    const q = useQuery(...);       q().data
// Svelte - stores (read with $):      const f = useFetcher("save");  $f.pending

Deferred data behavior

React, Preact, and Solid stream a <Await> boundary - the fallback flushes in the SSR HTML, then the resolved content streams in (it works with JS off). Vue and Svelte resolve deferred data on the client, so JS-off users see the fallback for that deferred boundary. Put critical page data in the route loader; use defer() for slower, non-critical data.

Dev HMR support

Dev HMR works for all five adapters through createViteDevServer and the framework's Vite plugin. React, Preact, and Vue preserve edited component state. Solid and Svelte update live without a full page reload, and the edited component may remount. Full matrix in Dev & HMR.