Typed Link
Type-safe Link components for React, Vue 3, and Svelte with route-name autocomplete and conditional routeParams.
<Link route="users.show" routeParams={{ id: '42' }}>View user</Link>The @dudousxd/nestjs-inertia-client package ships typed <Link> components for React, Vue 3, and Svelte. Each wraps the corresponding @inertiajs/* Link but adds a route prop autocompleted from your codegen output and a conditionally required routeParams prop typed to the exact parameters of that route.
Install
pnpm add @dudousxd/nestjs-inertia-clientBoot wiring
Each framework uses its idiomatic context/provider pattern to make the route() function available to all <Link> components in the tree.
import { InertiaRouteProvider } from '@dudousxd/nestjs-inertia-client/react';
import { route } from '~codegen/routes';
import { createInertiaApp } from '@inertiajs/react';
import { createRoot } from 'react-dom/client';
createInertiaApp({
setup({ el, App, props }) {
createRoot(el).render(
<InertiaRouteProvider routes={route}>
<App {...props} />
</InertiaRouteProvider>,
);
},
});Usage
import { Link } from '@dudousxd/nestjs-inertia-client/react';
// Route with no params — routeParams is omitted
<Link route="users.index">All users</Link>
// Route with params — routeParams is required and typed
<Link route="users.show" routeParams={{ id: '42' }}>View user</Link>Import from the /react subpath. The component accepts all standard @inertiajs/react Link props plus route and routeParams.
How the route name is chosen
The route name is composed from a class portion and a method portion, joined with a dot.
- Class portion: class-level
@As(...)if present, otherwise the class name with theControllersuffix stripped and first letter lowercased. - Method portion: method-level
@As(...)if present, otherwise the method name. - Final name:
${classPortion}.${methodPortion}
Class @As | Method @As | Derived name |
|---|---|---|
| absent | absent | <classNameStripped>.<methodName> |
@As('users') | absent | users.<methodName> |
| absent | @As('profile') | <classNameStripped>.profile |
@As('users') | @As('profile') | users.profile |
@As('users.admin') | @As('profile') | users.admin.profile |
The codegen reads the controller and derives the route name automatically. No defineContract or @ApplyContract needed:
@Controller('/users')
export class UsersController {
@Get(':id')
show(@Param('id') id: string) {
return this.usersService.findById(id);
}
}// Frontend — route name autocompleted, routeParams typed
<Link route="users.show" routeParams={{ id: user.id }}>View profile</Link>Override with @As at the class or method level:
import { As } from '@dudousxd/nestjs-inertia-client';
@Controller('/users')
@As('users')
class UsersController {
@Get('/:id')
@As('profile') // → 'users.profile'
show(@Param('id') id: string) {}
}Use the auto-derived name convention (UsersController.show -> users.show) so that route('users.show') and api.users.show both point to the same endpoint. One typed URL builder, one typed data fetcher.
Conditional routeParams
routeParams is typed as a mapped type derived from RouteParamsMap:
| Route pattern | routeParams | Behavior |
|---|---|---|
/users (no dynamic segments) | never | Must be omitted. TypeScript errors if you pass it. |
/users/:id (dynamic segments) | { id: string } | Required. TypeScript errors if you omit it. |
All routing mistakes are caught at compile time. No runtime "missing param" errors.
Typed Client
Auto-generated type-safe API client with queryOptions, mutationOptions, and queryKey for every controller endpoint. Zero configuration.
Forms & Validation
The full round-trip — the typed useForm hook on the client, class-validator DTOs on the server, and the InertiaValidationFilter that flashes a field-keyed error bag back into form.errors, byte-for-byte aligned with your field names.