Next.js
The @yak-io/nextjs package provides the best experience for Next.js applications with automatic route scanning and App Router integration.
Prerequisites
- Next.js 14+ (App Router)
- React 18+
- Node.js 18+
- Yak account + app ID
Installation
npm install @yak-io/nextjspnpm add @yak-io/nextjsyarn add @yak-io/nextjsbun add @yak-io/nextjsQuick Start
Configure environment
NEXT_PUBLIC_YAK_APP_ID=yak_app_123Get your app ID from the Yak dashboard.
Add the API handler
// app/api/yak/[[...yak]]/route.ts
import { createNextYakHandler } from "@yak-io/nextjs/server";
export const { GET, POST } = createNextYakHandler();Wrap your layout
// 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!}>
{children}
<YakWidget />
</YakProvider>
</body>
</html>
);
}By default, YakProvider fetches config from /api/yak (GET) and executes tool calls via /api/yak (POST).
You can override these with custom getConfig and onToolCall props if needed.
Route Configuration
By default, the handler scans ./src/app and ./src/pages, skips api/ folders, and feeds page routes to Yak. It also extracts title and description from static metadata exports in your page files.
Customize scanning
import { createNextYakHandler } from "@yak-io/nextjs/server";
export const { GET, POST } = createNextYakHandler({
appDir: "./app", // Override app directory
pagesDir: false, // Disable pages directory
routeFilter: {
include: [/^\/docs/], // Only include docs pages
exclude: [/^\/docs\/drafts/],
},
});Manual route sources
For full control, pass routes to override automatic scanning:
import { createNextYakHandler, scanRoutes } from "@yak-io/nextjs/server";
const marketingRoutes = {
id: "marketing",
getRoutes: async () => [
{ path: "/", title: "Home" },
{ path: "/pricing", title: "Pricing", description: "View our plans" },
],
};
export const { GET, POST } = createNextYakHandler({
routes: [() => scanRoutes("./src/app"), marketingRoutes],
});Adding Tools
Pass tool adapters to expose functionality to the AI:
import { createNextYakHandler } from "@yak-io/nextjs/server";
import { createTRPCToolAdapter } from "@yak-io/trpc";
import { appRouter, createContext } from "@/server/trpc";
const trpcTools = createTRPCToolAdapter({
router: appRouter,
createContext: async ({ req }) => createContext({ req }),
allowedProcedures: ["orders.list", "orders.detail"],
});
export const { GET, POST } = createNextYakHandler({
tools: [trpcTools],
});See Tool Adapters for more options.
Programmatic Control
Control the widget from any client component:
"use client";
import { useYak } from "@yak-io/nextjs/client";
export function HelpButton() {
const { open, close, openWithPrompt, isOpen } = useYak();
return (
<div>
<button onClick={() => open()}>Open Chat</button>
<button onClick={() => openWithPrompt("How do I use this feature?")}>
Get Help
</button>
{isOpen && <button onClick={() => close()}>Close</button>}
</div>
);
}useYak API
| Method | Description |
|---|---|
open() | Open the chat widget |
close() | Close the chat widget |
openWithPrompt(prompt) | Open with a specific prompt |
isOpen | Whether the widget is open |
Prompts sent via openWithPrompt are queued until the iframe is ready, preventing race conditions.
YakProvider Props
| Prop | Description |
|---|---|
appId | Yak application identifier |
getConfig | Async function returning chat config (default: fetches from /api/yak) |
onToolCall | Async function executing tool calls (default: POSTs to /api/yak) |
theme | Launcher + panel styling |
onRedirect | Custom navigation handler |
YakWidget Props
| Prop | Description |
|---|---|
iframeClassName | CSS class for the iframe |
triggerLabel | Button label (default: "Ask with AI") |
Common Patterns
Context-sensitive help
"use client";
import { useYak } from "@yak-io/nextjs/client";
import { usePathname } from "next/navigation";
export function ContextualHelp() {
const { openWithPrompt } = useYak();
const pathname = usePathname();
const getPrompt = () => {
if (pathname.includes("/billing")) return "Help me with billing";
if (pathname.includes("/settings")) return "Guide me through settings";
return "Help me with this page";
};
return (
<button onClick={() => openWithPrompt(getPrompt())}>
Get Help
</button>
);
}Error assistance
"use client";
import { useYak } from "@yak-io/nextjs/client";
export function ErrorFallback({ error }: { error: Error }) {
const { openWithPrompt } = useYak();
return (
<div>
<h2>Something went wrong</h2>
<button onClick={() => openWithPrompt(`Help me fix: ${error.message}`)}>
Get AI Assistance
</button>
</div>
);
}Route Manifest for Production (Required)
This setup is required for production deployments. Skip this only if you are providing your own custom routes via the routes or getRoutes options.
During local development, routes are scanned directly from the filesystem. 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, causing route discovery to fail.
Setup
Generate the manifest at build time
Add a prebuild script to your package.json:
{
"scripts": {
"prebuild": "yak-nextjs generate-manifest",
"build": "next build"
}
}This generates ./src/yak.routes.ts with your routes.
Different directory structure? The CLI auto-detects common layouts. For non-standard paths:
| Project structure | Command |
|---|---|
src/app/ (default) | yak-nextjs generate-manifest |
app/ | yak-nextjs generate-manifest --app-dir ./app --output ./app/yak.routes.ts |
src/app/ + src/pages/ | yak-nextjs generate-manifest --pages-dir ./src/pages |
app/ + pages/ | yak-nextjs generate-manifest --app-dir ./app --pages-dir ./pages --output ./app/yak.routes.ts |
Add to .gitignore
Since the file is generated at build time, add it to your .gitignore:
# Generated route manifest (use the path matching your --output)
src/yak.routes.ts
# or for root app/ directory:
# app/yak.routes.tsUse the route manifest adapter
// app/api/yak/[[...yak]]/route.ts
import { createNextYakHandler, createRouteManifestAdapter } from "@yak-io/nextjs/server";
import { routes } from "@/yak.routes"; // Adjust path based on your tsconfig paths
export const { GET, POST } = createNextYakHandler({
routes: createRouteManifestAdapter({ routes }),
});The import path depends on your tsconfig.json paths. Common configurations:
src/yak.routes.ts→@/yak.routes(when@/*maps to./src/*)app/yak.routes.ts→@/yak.routes(when@/*maps to./app/*) or use a relative path
The generated TypeScript module is imported directly, ensuring it's bundled with your serverless function automatically.
Filtering Routes
Use allowedRoutes and disallowedRoutes to control which routes are exposed:
import { createNextYakHandler, createRouteManifestAdapter } from "@yak-io/nextjs/server";
import { routes } from "@/yak.routes";
export const { GET, POST } = createNextYakHandler({
routes: 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.
CLI Options:
--app-dir <path>– App directory (default:./src/app)--pages-dir <path>– Pages directory (optional, scanned in addition to app-dir)--output <path>– Output file (default:./src/yak.routes.ts)
Alternative: Explicit Routes
If you prefer not to use filesystem scanning, provide routes directly:
export const { GET, POST } = createNextYakHandler({
routes: [
{ path: "/", title: "Home" },
{ path: "/pricing", title: "Pricing", description: "View our plans" },
],
});