Docs

Migrating from a meta-framework

Nifra is framework-agnostic, so you keep your UI library and replace the meta-framework around it: Next.js → Nifra + React, Nuxt → Nifra + Vue, SvelteKit → Nifra + Svelte, SolidStart → Nifra + Solid. The concepts map one-to-one, and you can migrate incrementally - stand up Nifra's API first, point your existing app at it, then move routes over.

How the concepts map

Meta-framework conceptNifra
File routes (`pages/`, `app/`, `routes/`)routes/ - `.tsx` / `.vue` / `.svelte` / `.mdx`, same dynamic [param] / [...catch-all] conventions
`getServerSideProps` · `load` · `createAsync` · `asyncData`export async function loader() in the route's .backend.ts half - runs on the server, typed into the page through loaderOutput
API routes (`pages/api`, `+server.ts`, route handlers)a server() backend + the typed client - no fetch() wrappers
Layouts (`layout.tsx`, `+layout`, `app.vue`)_layout.tsx - nested layout chains
Form actions / route handlers for mutationsexport async function action() - typed, progressive-enhancement forms
`getStaticProps` / `prerender` / `export const prerender`export const prerender = true (SSG) + ISR via withISR
`<Link>` · `<NuxtLink>` · `<a data-sveltekit-preload>`Nifra's client router - `Link` + hover/focus prefetch, scroll restoration
`next/image` · `nuxt/image`<Image> from @nifrajs/web-<fw>/image
`metadata` / `<Head>` / `definePageMeta`export const meta (or a meta() function of the loader data)

Data loading (Next.js → Nifra + React)

The biggest change: data fetching becomes a typed loader that calls your backend in-process during SSR - no fetch to your own API, no untyped JSON.

TSX
// Next.js - app/users/[id]/page.tsx
export default async function Page({ params }) {
  const res = await fetch(`https://api/users/${params.id}`)
  const user = await res.json()
  return <h1>{user.name}</h1>
}

// Nifra - routes/users/[id].backend.ts: the loader, which never reaches the browser
export const loaderOutput = t.object({ name: t.string() })   // the only fields the page receives
export async function loader({ params, api }: Route.LoaderArgs) {
  const res = await api.users({ id: params.id }).get()   // typed, in-process during SSR
  if (!res.ok) throw notFound()
  return res.data
}

// Nifra - routes/users/[id].tsx: the page
export default function User({ data }: Route.ComponentProps) {
  return <h1>{data.name}</h1>
}

Svelte / Solid / Vue

Identical shape - only the page file's extension and component syntax change. The route's .backend.ts half (loader, action, loaderOutput) is the same file on every framework (that's Nifra's render seam). SvelteKit's +page.server.ts maps onto it one to one:

TS
// SvelteKit - +page.server.ts + +page.svelte
export async function load({ params }) { return { post: await getPost(params.slug) } }

// Nifra - routes/blog/[slug].backend.ts + routes/blog/[slug].svelte
export const loaderOutput = t.object({ post: t.object({ title: t.string(), html: t.string() }) })
export async function loader({ params }: Route.LoaderArgs) { return { post: await getPost(params.slug) } }

What Nifra adds

  • One model, any UI library - switch React→Solid later by changing one import, not your app.
  • Much faster SSR - the current dynamic-page snapshot reports Nifra at 24× the throughput of Next.js, 7× Nuxt, and 4× SvelteKit / 3.9× SolidStart, with a fraction of the client JavaScript. See benchmarks.
  • End-to-end types with no codegen, the same app on Bun / Node / Deno / the edge, and a backend you can also ship on its own. Start with Getting started.

Moving a backend (Express, Hono, Fastify, Elysia) instead? See Migrating a backend.