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 concept | Nifra |
|---|---|
| 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 mutations | export 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.
// 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:
// 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.