@yak-io/nextjs
v0.3.0
Published
Next.js SDK for embedding yak chatbot with route manifest generation
Downloads
92
Maintainers
Readme
@yak-io/nextjs
Next.js integration layer for the Yak embeddable chat widget. This package focuses on the App Router plumbing (route scanning, CLI helpers, and ergonomic handler factories) while delegating the widget runtime to @yak-io/react and @yak-io/javascript.
Relationship to @yak-io/javascript and @yak-io/react
@yak-io/javascriptexposes the framework-agnostic client logic (iframe communication, tool execution) and server utilities (createYakHandler,RouteSource,ToolSource, etc.).@yak-io/reactexposes the React provider/widget and hooks.@yak-io/nextjsre-exports the client entrypoint for convenience and layers Next-specific helpers on top of the core primitives (route scanners, manifest CLI, handler factories that accept either simple callbacks or rich adapters).
If you are not on Next.js you can depend on @yak-io/react directly and provide your own routing/tool adapters.
Installation
pnpm add @yak-io/nextjsThe widget core is pulled in automatically as a dependency.
Quickstart
1. Client widget
// app/layout.tsx
import { YakProvider, YakWidget } from "@yak-io/nextjs/client";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<YakProvider
appId={process.env.NEXT_PUBLIC_YAK_APP_ID!}
theme={{ position: "bottom-right" }}
>
{children}
<YakWidget />
</YakProvider>
</body>
</html>
);
}2. Unified API route
// app/api/yak/[...yak]/route.ts
import { createNextYakHandler } from "@yak-io/nextjs/server";
export const { GET, POST } = createNextYakHandler({
// Routes auto-scan from ./src/app and ./src/pages
});The handler responds to GET with route metadata (and optional tool manifest) and to POST with tool execution.
3. Programmatic control
Control the chat widget from your code:
// app/my-page/page.tsx
"use client";
import { useYak } from "@yak-io/nextjs/client";
export default function MyPage() {
const chat = useYak();
return (
<div>
<button onClick={() => chat.open()}>Open Chat</button>
<button onClick={() => chat.openWithPrompt("Explain this product")}>
Product Help
</button>
<button onClick={() => chat.openWithPrompt("How do I use this feature?")}>
Feature Help
</button>
{chat.isOpen && <p>Chat is open</p>}
</div>
);
}4. Add tools (tRPC example)
import { createNextYakHandler } from "@yak-io/nextjs/server";
import { createTRPCToolAdapter } from "@yak-io/trpc";
import { appRouter, createContext } from "@/server/trpc";
const trpcTools = createTRPCToolAdapter({
id: "trpc",
router: appRouter,
createContext: async ({ req }) => createContext({ req }),
allowedProcedures: ["orders.list", "orders.detail"],
});
export const { GET, POST } = createNextYakHandler({
tools: trpcTools,
});Route & tool injection
createNextYakHandler auto-scans ./src/app and ./src/pages, skips api/ folders, and feeds the manifest to Yak. Override the directories or clamp the manifest with regex filters:
import { createNextYakConfigHandler } from "@yak-io/nextjs/server";
export const { GET } = createNextYakConfigHandler({
appDir: "./app",
pagesDir: "./pages",
routeFilter: {
include: [/^\/docs/],
exclude: [/^\/docs\/draft/],
},
});Providing routeFilter.include means at least one regex must match the path; routeFilter.exclude removes any matches. If you set routes or getRoutes the auto-scanner is bypassed, allowing you to compose your own sources:
import type { RouteSource } from "@yak-io/javascript/server";
import {
createNextYakHandler,
scanRoutes,
} from "@yak-io/nextjs/server";
import { createTRPCToolAdapter } from "@yak-io/trpc";
const marketingRoutes: RouteSource = {
id: "marketing",
getRoutes: async () => [
{ path: "/docs", title: "Documentation", description: "Product docs" },
],
};
const graphqlTools = {
id: "graphql",
getTools: async () => [{ name: "accounts.lookup", description: "Fetch account" }],
executeTool: async (name, args) => {
const res = await fetch("https://graphql.example.com", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: buildQueryFor(name), variables: args }),
});
return res.json();
},
};
export const { GET, POST } = createNextYakHandler({
routes: [() => scanRoutes("./src/app"), marketingRoutes],
tools: [createTRPCToolAdapter(trpcConfig), graphqlTools],
});Behind the scenes @yak-io/javascript merges every RouteSource into a single manifest and wires each tool definition to the adapter that registered it.
CLI: route manifest generator
yak-nextjs generate-manifestUse the CLI to generate a TypeScript module with your routes at build time.
Options:
--app-dir <path>– Path to Next.js app directory (default:./src/app)--pages-dir <path>– Path to Next.js pages directory (optional, scanned in addition to app-dir)--output <path>– Output file path (default:./src/yak.routes.ts)
Examples:
yak-nextjs generate-manifest
yak-nextjs generate-manifest --pages-dir ./src/pages
yak-nextjs generate-manifest --app-dir ./app --output ./src/generated/routes.tsRoute Manifest for Production
During local development, routes are scanned directly from the filesystem—no setup needed. However, when you build your Next.js app for production, only the compiled .next output is included. Source files like ./src/app are not present at runtime.
Recommended setup
- Add a
prebuildscript to generate the manifest before Next.js builds:
{
"scripts": {
"prebuild": "yak-nextjs generate-manifest",
"build": "next build"
}
}- Use the route manifest adapter in your handler:
// app/api/yak/[...yak]/route.ts
import { createNextYakHandler, createRouteManifestAdapter } from "@yak-io/nextjs/server";
import { routes } from "@/yak.routes";
export const { GET, POST } = createNextYakHandler({
routes: createRouteManifestAdapter({ routes }),
});The generated TypeScript module is imported directly, ensuring it's bundled with your serverless function automatically.
Route filtering
Use allowedRoutes and disallowedRoutes to control which routes are exposed (similar to tRPC adapter):
createRouteManifestAdapter({
routes,
allowedRoutes: ["/docs/*", "/pricing", "/"],
disallowedRoutes: ["/docs/internal/*"],
})Pattern matching:
- Exact match:
"/pricing"matches only/pricing - Prefix match:
"/docs/*"matches/docs,/docs/getting-started, etc.
Alternative: explicit routes
If you prefer not to use filesystem scanning, provide routes explicitly:
export const { GET, POST } = createNextYakHandler({
routes: [
{ path: "/", title: "Home" },
{ path: "/pricing", title: "Pricing" },
],
});API surface (server)
@yak-io/nextjs/server exports:
scanRoutes(directory: string, options?: { directoryType?: "app" | "pages" })– low-level filesystem scanner (useful for precomputing manifests or composing custom sources). Only captures page routes, extractingtitleanddescriptionfrom static metadata exports.loadRouteManifest(path?: string)– load a pre-built JSON manifest via filesystem read, returnsnullif not found.loadRoutes(path?: string)– load routes from a manifest, throws with helpful error if not found.createNextYakHandler(config)– unified GET + POST handler. Whenroutes/getRoutesare omitted it auto-scans./src/app(override withappDir). In production, automatically fetches manifest from public folder.createNextYakConfigHandler(config)– GET-only convenience wrapper.createNextYakToolsHandler(config)– POST-only wrapper.- Re-exported types from
@yak-io/javascript/server(RouteInfo, RouteManifest, ToolDefinition, ToolManifest, ToolExecutor, ChatConfig, RouteSourceInput, ToolSourceInput, etc.).
Client props
YakProvider accepts:
| Prop | Type | Description |
| --- | --- | --- |
| appId | string | Yak app identifier |
| getConfig | () => Promise<ChatConfig> | Config provider (default fetches /api/yak) |
| onToolCall | (name, args) => Promise<unknown> | Tool call handler (default POSTs to /api/yak) |
| theme | Theme | Floating button + panel theme (position, colorMode, colors) |
| onRedirect | (path: string) => void | Custom navigation handler |
| disableRestartButton | boolean | Hide the restart session button in the header |
Hooks
useYak
Access the widget API from any component:
import { useYak } from "@yak-io/nextjs/client";
export function ChatButton() {
const { open, close, openWithPrompt, isOpen } = useYak();
return <button onClick={() => open()}>Open Chat</button>;
}useYakToolEvent
Subscribe to tool call completion events for page-level cache invalidation:
import { useYakToolEvent } from "@yak-io/nextjs/client";
import { trpc } from "@/utils/trpc";
function PlanPage({ planId }: { planId: string }) {
const utils = trpc.useUtils();
useYakToolEvent((event) => {
// Invalidate cache when plan-related tools are called
if (event.ok && event.name.startsWith("plan.")) {
utils.plan.get.invalidate({ id: planId });
utils.planItem.list.invalidate({ planId });
}
});
// ... component rendering
}The event object contains:
| Property | Type | Description |
| --- | --- | --- |
| name | string | The tool name that was called |
| args | unknown | The arguments passed to the tool |
| ok | boolean | Whether the call succeeded |
| result | unknown | The result (if ok is true) |
| error | string | The error message (if ok is false) |
YakWidget renders the launcher + iframe shell. It accepts:
| Prop | Type | Description |
| --- | --- | --- |
| iframeClassName | string | Optional CSS class for the iframe |
| triggerLabel | string | Text to display next to the logo (default "Ask with AI") |
Using @yak-io/javascript directly
If you need to integrate with Remix, SvelteKit, or a custom Node runtime you can:
import { createYakHandler } from "@yak-io/javascript/server";
export const { GET, POST } = createYakHandler({
routes: [{ id: "remix", getRoutes: listRoutes }],
tools: [{ id: "graphql", getTools, executeTool }],
});The React provider/widget are also exported from @yak-io/react.
Migration notes
- Existing imports from
@yak-io/nextjs/clientcontinue to work, but the actual implementation lives in@yak-io/react. createNextYakHandlerauto-scans./src/appand./src/pages; configure viaappDir,pagesDir, or regex-basedrouteFilter.createNextYakHandleracceptsroutes/toolsarrays. LegacygetRoutes/getTools/executeToolstill work and are normalized internally.- For custom adapters prefer using
@yak-io/javascript/servertypes so your code can be reused outside Next.js.
License
Proprietary - see LICENSE file
