Aviary
Guides

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-client

Boot 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 the Controller suffix stripped and first letter lowercased.
  • Method portion: method-level @As(...) if present, otherwise the method name.
  • Final name: ${classPortion}.${methodPortion}
Class @AsMethod @AsDerived name
absentabsent<classNameStripped>.<methodName>
@As('users')absentusers.<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 patternrouteParamsBehavior
/users (no dynamic segments)neverMust 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.

On this page