@dudousxd/nestjs-inertia

中文文档
3.0.1发布于 2026/11/12MITTypeScriptESM + CJS构建来源已验证近 30 天下载 2796

Inertia.js adapter for NestJS — core protocol and module.

原始使用文档
文档目录17 节
  1. Install
  2. Quick start
  3. Controllers
  4. Async config
  5. Multi-app with forFeature
  6. Template engines
  7. CSRF
  8. Fastify
  9. Prop markers
  10. Auto-included infrastructure
  11. Opt-in utilities
  12. FlashStore (errors)
  13. SSR
  14. Generated files in the server bundle
  15. Protocol parity
  16. Companion packages (planned)
  17. License

@dudousxd/nestjs-inertia

Inertia.js v3 adapter for NestJS — Express + Fastify, multi-app via forFeature, 4 template engines, CSRF protection, full Inertia v3 protocol parity.

npm license

Install

pnpm add @dudousxd/nestjs-inertia

# Pick your HTTP platform
pnpm add express @types/express          # Express adapter (default)
# OR
pnpm add fastify @nestjs/platform-fastify @fastify/cookie  # Fastify adapter

Quick start

// app.module.ts
import { Module } from '@nestjs/common';
import { InertiaModule } from '@dudousxd/nestjs-inertia';

@Module({
  imports: [
    InertiaModule.forRoot({
      version: () => process.env.ASSET_VERSION ?? 'dev',
      rootView: 'inertia/root.html',
      share: async (req) => ({ auth: req.user ?? null }),
    }),
  ],
})
export class AppModule {}

inertia/root.html:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <title>My App</title>
  @inertiaHead
  @vite('app/client.tsx')
  @viteRefresh
</head>
<body>
  @inertia
</body>
</html>

Controllers

Two equivalent patterns coexist:

import { Controller, Get, Req } from '@nestjs/common';
import { Inertia } from '@dudousxd/nestjs-inertia';
import type { Request } from 'express';

@Controller()
export class HomeController {
  // Decorator pattern (idiomatic for new code)
  @Get('/')
  @Inertia('Home')
  show() {
    return { hello: 'world' };
  }

  // Imperative pattern (use when you need fine control)
  @Get('/crew')
  async list(@Req() req: Request) {
    await req.inertia
      .share({ flash: req.session?.flash ?? {} })
      .render('Crew', { crew: await this.svc.list() });
  }
}

Async config

InertiaModule.forRootAsync({
  imports: [ConfigModule],
  inject: [ConfigService],
  useFactory: (cfg: ConfigService) => ({
    version: cfg.get('ASSET_VERSION'),
    rootView: 'inertia/root.html',
  }),
});

Three paths: useFactory + inject (most common), useClass, useExisting.

Multi-app with forFeature

Host two or more Inertia apps in the same NestJS process — each with its own Vite entry, shell, version, share, SSR bundle. Useful for admin panels, multi-tenant white-label, or migrations between frontend stacks.

@Module({
  imports: [
    InertiaModule.forRoot({                  // main app
      vite: { entry: 'app/client.tsx' },
      rootView: 'inertia/root.html',
      share: req => ({ auth: req.user }),
    }),
    InertiaModule.forFeature({               // admin app
      scope: 'admin',
      vite: { entry: 'admin/client.tsx' },
      rootView: 'inertia/admin-root.html',
      share: req => ({ admin: req.adminContext }),
    }),
  ],
})
export class AppModule {}

Select scope per controller / method with @UseInertia('scope'):

@Controller('admin')
@UseInertia('admin')
export class AdminDashboardController {
  @Get('/')
  @Inertia('AdminDashboard')
  show() { return { stats: ... }; }
}

forFeatureAsync works the same as forRootAsync (useFactory/useClass/useExisting). Reserved scope: 'default' is owned by forRoot().

Template engines

rootView accepts .html (own parser) plus .hbs / .ejs / .pug / .liquid if the engine package is installed:

pnpm add handlebars                    # or: ejs, pug, liquidjs
InertiaModule.forRoot({
  rootView: 'inertia/root.hbs',         // auto-detects Handlebars
});

Each engine sees locals { page, inertia, inertiaHead, vite, viteRefresh, asset }. Use the engine's own escape rules (e.g., {{{inertia}}} triple-stache in Handlebars, <%- inertia %> in EJS, != inertia in Pug). The @inertia/@inertiaHead/@vite/@viteRefresh/@asset directives are also expanded in the engine's output, root view and partials/includes alike, so you can mix and match. The page JSON, the SSR markup and any text printed from page are restored after that pass, so page data is never treated as a directive.

CSRF

import { CsrfCookieInterceptor, CsrfGuard } from '@dudousxd/nestjs-inertia';

// Global cookie writer
app.useGlobalInterceptors(new CsrfCookieInterceptor({ secret: process.env.CSRF_SECRET }));

// Per-route validation
@UseGuards(new CsrfGuard({ secret: process.env.CSRF_SECRET }))
@Post('/profile')
async update() { ... }

Cookie name XSRF-TOKEN, header name X-XSRF-TOKEN — both match the Inertia client convention. Signed via HMAC-SHA256. Requires cookie-parser (Express) or @fastify/cookie (Fastify) as peer dep.

Fastify

import { FastifyAdapter } from '@nestjs/platform-fastify';

const app = await NestFactory.create<NestFastifyApplication>(AppModule, new FastifyAdapter());

Full feature parity with Express: middleware, decorator, all interceptors, guards, filters, shell directives, SSR, FlashStore. request.inertia is wired via decorateRequest + onRequest hook automatically when the FastifyAdapter is detected.

Prop markers

import { Inertia } from '@dudousxd/nestjs-inertia';

return {
  user: Inertia.always(() => currentUser()),
  stats: Inertia.optional(() => heavyStatsCalculation()),
  activity: Inertia.defer(() => activityFeed(), 'secondary'),
  rows: Inertia.merge(() => paginated(p), { matchOn: 'id', deep: false }),
  csrfToken: Inertia.once(() => generateToken()),
};
  • always — resolves on every render (including partial reloads)
  • optional — resolves only when listed in X-Inertia-Partial-Data
  • defer — listed in page.deferredProps; client v2 dispatches a follow-up request
  • merge — resolves + marks as merge target (client appends/replaces)
  • once — resolves on first visit, cached until X-Inertia-Reset-Once lists the key

Auto-included infrastructure

forRoot() installs:

  • InertiaMiddleware (Express) or FastifyInertiaPlugin (Fastify) — req.inertia available everywhere
  • MethodSpoofMiddleware (POST + multipart + _method=PUT/PATCH/DELETE)
  • RedirectInterceptor (302 → 303 upgrade on PUT/PATCH/DELETE Inertia requests)
  • InertiaRenderInterceptor (handles @Inertia('Page') decorator)
  • InertiaScopeSwitcherInterceptor (handles @UseInertia('scope'))

Disable via knobs:

InertiaModule.forRoot({
  methodSpoofing: false,
  autoUpgrade303: false,
});

Opt-in utilities

import { ErrorBagInterceptor } from '@dudousxd/nestjs-inertia';

// Error bag interceptor — opt-in per route
@UseInterceptors(ErrorBagInterceptor)

Auth-redirect guards and not-found filters are not shipped exports — build your own following the auth-redirect and not-found recipes.

FlashStore (errors)

NestJS has no session of its own. Plug an adapter:

import type { FlashStore } from '@dudousxd/nestjs-inertia';

class ExpressSessionFlashStore implements FlashStore {
  read(req) {
    return (req as Request).session?.flash?.errors ?? {};
  }
}

InertiaModule.forRoot({
  flashStore: new ExpressSessionFlashStore(),
});

SSR

InertiaModule.forRoot({
  ssr: {
    enabled: process.env.NODE_ENV === 'production',
    bundlePath: 'dist/inertia/ssr/ssr.mjs',
    throwOnError: false,
  },
});

Bundle must export default { render(page) } or named render(page) returning { head: string[], body: string }.

Generated files in the server bundle

Only if your server bundle imports generated files, copy them with nest-cli.json:

{
  "compilerOptions": {
    "assets": [".nestjs-inertia/**/*"],
    "watchAssets": true
  }
}

Protocol parity

Full Inertia protocol: X-Inertia headers, version mismatch (409 + X-Inertia-Location, GET only), partial reloads, deferred props, merge/deepMerge with matchOn, once, history encryption / clear, error bags, X-Inertia-Reset, X-Inertia-Partial-Except, X-Inertia-Reset-Once, dot-notation unpacking, undefined→null wire conversion.

Companion packages (planned)

  • @dudousxd/nestjs-inertia-vite — Vite dev/build helpers (Plan B)
  • @dudousxd/nestjs-inertia-testing — expectInertia(res) matchers (Plan B)
  • @dudousxd/nestjs-inertia-codegen — typed pages (Plan C)
  • @dudousxd/nestjs-inertia-client — Tuyau-style typed REST + TanStack Query (Plan D)
  • Examples + docs site + CI workflows (Plan E)

See docs/design.md for full design.

License

MIT © Davi Carvalho

版本与发布记录

@dudousxd/nestjs-inertia 共发布 33 个版本,首次发布于 2026/05/24;过去 12 个月发布了 33 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
3.0.1latest2026/11/121 天前
1.9.42026/11/094 天前
1.9.32026/11/085 天前
1.9.22026/10/2717 天前
1.9.12026/10/2023 天前
1.9.02026/09/301 个月前

关于 @dudousxd/nestjs-inertia 的常见问题

@dudousxd/nestjs-inertia 是什么?

Inertia.js adapter for NestJS — core protocol and module.

如何安装 @dudousxd/nestjs-inertia?

在项目目录运行 npm install @dudousxd/nestjs-inertia,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:rxjs(>=7.0.0)、@nestjs/core(>=10.0.0)、@nestjs/common(>=10.0.0)、reflect-metadata(>=0.1.13)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@dudousxd/nestjs-inertia 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@dudousxd/nestjs-inertia 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@dudousxd/nestjs-inertia 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=24。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@dudousxd/nestjs-inertia 可以商用吗?

@dudousxd/nestjs-inertia 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@dudousxd/nestjs-inertia 的下载量有多少?

根据 npm 官方下载统计,@dudousxd/nestjs-inertia 在 2026/10/13 – 2026/11/11 这 30 天内被下载 2,796 次,日均约 93 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@dudousxd/nestjs-inertia 还在维护吗?

最新版本 3.0.1 发布于 2026/11/12(1 天前);至今共发布 33 个版本,其中过去 12 个月发布了 33 个;首次发布于 2026/05/24。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。