Client setup

Configure Vite and the React, Vue or Svelte client for an Inertia.Net app.

On this page

Inertia.Net speaks the Inertia.js v3 protocol. Install a v3 client package; v2 clients are not supported.

Install the client

Run client setup commands from the ASP.NET Core project directory (the one containing your .csproj). npm, Bun, pnpm and Yarn are supported. The framework snippets use npm spelling; replace the command with the equivalent for your package manager:

Package managerAdd packagesAdd development packages
npmnpm install <packages>npm install -D <packages>
Bunbun add <packages>bun add -D <packages>
pnpmpnpm add <packages>pnpm add -D <packages>
Yarnyarn add <packages>yarn add -D <packages>

Replace <packages> with the names and version ranges from your framework's snippet. If the project has no package.json, create one first with npm init -y, bun init -y, pnpm init or yarn init.

Text
npm install react react-dom @vitejs/plugin-react @inertiajs/react@^3
npm install -D vite typescript @types/node @types/react @types/react-dom

@types/node types the node:fs import and process in the Vite config. Add vite/client to tsconfig.json so import.meta.glob is typed:

Text
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "jsx": "react-jsx",
    "strict": true,
    "noEmit": true,
    "skipLibCheck": true,
    "types": ["vite/client", "node"]
  },
  "include": ["resources/js", "vite.config.ts"]
}

Use "jsx": "preserve" or omit it for Vue and Svelte.

The starter templates use ClientApp/pages. The manual examples below use resources/js/Pages. Keep the resolver, file paths and Vite entry consistent with the layout you choose.

The templates also ship ClientApp/lib/inertia-signalr.ts, the client transport for live props over SignalR. Uncomment live: signalr() in ClientApp/app.tsx (or app.ts) to turn it on.

Vite configuration

The same Vite config works for every client; only the framework plugin changes.

Text
// vite.config.ts
import { defineConfig, type Plugin } from 'vite'
import react from '@vitejs/plugin-react'            // Vue: @vitejs/plugin-vue, Svelte: @sveltejs/vite-plugin-svelte
import { mkdirSync, rmSync, writeFileSync } from 'node:fs'

// Writes the dev server URL to wwwroot/hot. Inertia.Net reads it in Development
// and renders the dev-server tags instead of the manifest tags.
function hotFile(): Plugin {
  const file = 'wwwroot/hot'
  return {
    name: 'inertia-net-hot-file',
    apply: 'serve',
    configureServer(server) {
      server.httpServer?.once('listening', () => {
        const address = server.httpServer!.address()
        if (address && typeof address === 'object') {
          mkdirSync('wwwroot', { recursive: true })
          writeFileSync(file, `http://localhost:${address.port}`)
        }
      })
      const clean = () => rmSync(file, { force: true })
      process.on('exit', clean)
      process.on('SIGINT', () => process.exit())
      process.on('SIGTERM', () => process.exit())
    },
  }
}

export default defineConfig(({ command }) => ({
  plugins: [react(), hotFile()],
  // Built assets live at /build/. The dev server serves from the root because
  // Inertia.Net emits {origin}/@vite/client and {origin}/{entry}.
  base: command === 'build' ? '/build/' : '/',
  server: { port: 5173, strictPort: true, origin: 'http://localhost:5173' },
  build: {
    outDir: 'wwwroot/build',
    emptyOutDir: true,
    manifest: true,
    rollupOptions: { input: 'resources/js/app.tsx' },
  },
}))

Add these fields to package.json:

Text
{ "type": "module", "scripts": { "dev": "vite", "build": "vite build" } }

The manifest key of the entry (resources/js/app.tsx) is what you pass to @vite("...").

Client entry

The optional @inertiajs/vite plugin resolves pages and mounts the app for you. With it, the entry is just:

Text
import { createInertiaApp } from '@inertiajs/react'   // or @inertiajs/vue3, @inertiajs/svelte

createInertiaApp()

Without the plugin, provide the resolver and setup yourself.

Text
// resources/js/app.tsx
import { createInertiaApp, type ResolvedComponent } from '@inertiajs/react'
import { createRoot } from 'react-dom/client'

createInertiaApp({
  resolve: (name) => {
    const pages = import.meta.glob<{ default: ResolvedComponent }>('./Pages/**/*.tsx', { eager: true })
    return pages[`./Pages/${name}.tsx`]
  },
  setup({ el, App, props }) {
    if (!el) throw new Error('Inertia root element not found')
    createRoot(el).render(<App {...props} />)
  },
})

Pages

A page file receives the props from Render(...) as normal component props.

Text
// resources/js/Pages/Users/Index.tsx
import { Link } from '@inertiajs/react'

export default function Index({ users }: { users: { id: number; name: string }[] }) {
  return (
    <ul>
      {users.map((u) => (
        <li key={u.id}><Link href={`/users/${u.id}`}>{u.name}</Link></li>
      ))}
    </ul>
  )
}

Use the generated types

Inertia.Net can write TypeScript declarations for your page props, shared props and flash data. See TypeScript types.

Development workflow

Text
npm run dev          # or bun/pnpm/yarn run dev; Vite writes wwwroot/hot
dotnet watch         # terminal 2: the app, ASPNETCORE_ENVIRONMENT=Development

Open the ASP.NET Core URL, not the Vite URL. Two servers run side by side:

URLWhat it isOpen it?
http://localhost:5180 (the template's default)Your app. ASP.NET Core routes the request, runs your endpoint and renders the Inertia page.Yes
http://localhost:5173The Vite dev server. It only serves JavaScript, CSS and hot-reload updates to the page above.No. It has no pages, so it shows a blank page.

Vite prints localhost:5173 prominently when it starts, but that address is for the browser's asset requests, not for you. In Development the app reads wwwroot/hot and renders the Vite tags; in production it reads the manifest.

For production:

Text
npm run build        # or bun/pnpm/yarn run build
dotnet publish -c Release

Node is needed to build the assets and only needed at runtime when SSR is enabled.

Using @inertiajs/vite

The plugin also contributes the SSR dev endpoint and page resolution. Add it before the hot-file plugin:

Text
import inertia from '@inertiajs/vite'

export default defineConfig({
  plugins: [
    react(),
    inertia({ ssr: { entry: 'resources/js/ssr.tsx', host: '127.0.0.1', port: 13714 } }),
    hotFile(),
  ],
})

Keep the SSR listener on loopback: the renderer trusts its output and has no authentication. The SSR guide covers the full setup.