# @assistant-ui/next Next.js integration for assistant-ui: the `withAui()` config wrapper and the compiler for the `"use generative"` directive. Colocate a tool's **schema**, **server-only `execute`**, and **client-only `render`** in one file; the compiler emits a different module per build target so each side only loads what it needs. See [SPEC.md](./SPEC.md) for the full design. ## Why `"use client"` is whole-module, so it can't keep a tool's zod schema readable on the server while keeping its `render` on the client. And a backend `execute` holds secrets (DB handles, API keys) that must never reach the browser bundle. `"use generative"` routes each property to the right place. Every tool **must** declare an `execute`, and you wrap the default export in `defineToolkit({ ... })` (both are enforced — the compiler errors otherwise). You don't declare a tool's kind: the compiler **infers** it from the `execute` and writes a `type` field back into the output. | how you author the `execute` | kind | where it runs | | ----------------------------------------- | ---------- | ---------------------------- | | `execute` with a `"use client"` directive | `frontend` | client | | `execute` (plain) | `backend` | server (`server-only` guard) | | `execute: humanTool()` | `human` | — (the UI supplies a result) | A plain `execute` is server-only by default — you can only run one in the browser by opting in with `"use client"`, so secrets can't leak by omission. ## Authoring ```tsx "use generative"; import { z } from "zod"; import { defineToolkit } from "@assistant-ui/react"; import { db } from "@/db"; // server-only import { Chart } from "@/ui/chart"; // client-only export default defineToolkit({ weather: { description: "Show the weather for a city.", parameters: z.object({ city: z.string() }), execute: async ({ city }) => db.weather.get(city), // backend → stays on the server render: (props) => , // stays on the client }, }); ``` The server build keeps `parameters` + `execute` (guarded by `import "server-only"`, tagged `type: "backend"`) and drops `render` and `@/ui/chart`. The client build keeps `parameters` + `render` (under `"use client"`) and drops `execute` and `@/db`. A `frontend` tool marks its `execute` with `"use client"`: ```tsx execute: async ({ city }) => { "use client"; return navigator.geolocation /* … runs in the browser, kept client-side */; }, ``` ## Wiring into Next.js Wrap your config. Detection is by the `"use generative"` directive — there is **no filename convention**; modules without the directive pass through untouched. ```ts // next.config.ts import { withAui } from "@assistant-ui/next"; export default withAui({ /* your Next config */ }); ``` `withAui` applies the loader to your TS/TSX. To limit how many files it scans, narrow the globs: `withAui(config, { rules: ["*.generative.tsx"] })`. Import the module **bare** from both sides — the loader rewrites it into a facade that resolves to the right build per layer (no query, no per-file config): ```tsx // a client component → resolves to the client build (schema + render) import toolkit from "@/lib/chat.generative"; // a route handler (react-server layer) → resolves to the server build (schema + execute) import toolkit from "@/lib/chat.generative"; ``` With the AI SDK, convert the server build to a `ToolSet` (see `AISDKToolkit` in `@assistant-ui/react-ai-sdk`). > **Validated on Next 16.2.6 (Turbopack).** Turbopack honors the loader-emitted > `"use client"`, but compiles one output per resource path — so the server build > is selected by its own `?generative-env=server` query rather than by build layer. > Clear `.next` after changing the loader (Turbopack caches loader output). ## License MIT