Server-driven SPAs, native to NestJS.
Build React, Vue, or Svelte frontends on your NestJS monolith — no API to design, no client cache to sync. Pages render straight from your controllers, with a typed <Link>, page-name autocomplete, and end-to-end props along for the ride. Express and Fastify.
5 packages on npm · `nestjs-inertia init` scaffolds everything · auto-codegen in dev
Your controller is the page.
Return an object from a controller, tag it with @Inertia, and that data lands in your page component — fully typed, no fetch, no API route, no client cache to babysit. The response is the page.
@Controller('dashboard')export class DashboardController { @Get() @Inertia('Dashboard') // typed page name getDashboard() { return { user, stats }; // → the page props }}// props inferred from getDashboard() — no manual typesexport default function Dashboard({ user, stats }: PageProps) { return ( <section> <h1>Hi, {user.name}</h1> <Link href="/settings">Settings</Link> // typed route </section> );}// no fetch · no useQuery · no API routeGot consumers that aren't Inertia?
Inertia covers your own frontend. For mobile apps, third parties, or anything that calls your NestJS API directly, its sibling @dudousxd/nestjs-codegen generates a fully typed client from the same controllers — TanStack Query options, typed params and bodies, the works.
// manual axios hooks everywhereexport function useGetCrew() { return useQuery({ queryKey: ['crew'], queryFn: async () => { const { data } = await axios.get('/api/v1/crew/list'); return data as CrewResponse; }, });}// ...repeat for every endpointimport { api } from '~codegen/api'; // Queries — fully typed, zero boilerplateconst { data } = useQuery(api.crew.getCrew.queryOptions()); // Mutations — body & params typed from your controllerconst update = useMutation(api.crew.updateCrew.mutationOptions());await update.mutateAsync({ params: { id: '42' }, body: { name: 'New Name' } }); // Cache invalidation — typed keysqc.invalidateQueries({ queryKey: api.crew.getCrew.queryKey() });The monolith, with a typed wire
Six guarantees, one mental model. Everything between your NestJS controllers and your pages is generated and type-checked.
Server-driven, no API
Controllers return page props; Inertia ships them with the page render. No REST or GraphQL layer to design, version, or keep in sync — and no client data cache to invalidate.
Typed <Link>
Route names autocompleted from your controllers. routeParams required and typed to the exact params. Wrong route or missing param? Compile-time error.
Type-safe page names
The codegen scans inertia/pages/ and augments @Inertia() to only accept pages that exist. A typo like 'Dashbord' fails the build — not the user.
Props E2E
req.inertia.render() knows what each page expects — props inferred straight from the default export of every page component. Wrong props never ship.
React, Vue, or Svelte
One adapter, three frameworks, full Inertia protocol parity: partial reloads, deferred props, history encryption, error bags — on Express and Fastify.
First-class testing
expectInertia() matchers assert the rendered page, props, and redirects in supertest e2e suites — without booting a browser.
One command. That's the install.
nestjs-inertia init scaffolds the inertia/ directory and auto-patches your existing app.module.ts and main.ts. From then on, nest start --watch is the only dev command you need — codegen watches alongside it.
$ pnpm add @dudousxd/nestjs-inertia \ @dudousxd/nestjs-inertia-vite @dudousxd/nestjs-inertia-client $ npx nestjs-inertia init ✓ inertia/ scaffolded (pages, app shell, vite entry)✓ app.module.ts patched — InertiaModule.forRoot()✓ main.ts patched — setupInertiaVite()✓ nestjs-inertia.config.ts created — auto-codegen on dev bootShip pages, not endpoints.
Install, run init, and build server-driven SPAs on NestJS — React, Vue, or Svelte, with the compiler watching your pages, links, and props.