Yak Docs
Chat Integration

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/nextjs
pnpm add @yak-io/nextjs
yarn add @yak-io/nextjs
bun add @yak-io/nextjs

Quick Start

Configure environment

NEXT_PUBLIC_YAK_APP_ID=yak_app_123

Get 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

MethodDescription
open()Open the chat widget
close()Close the chat widget
openWithPrompt(prompt)Open with a specific prompt
isOpenWhether the widget is open

Prompts sent via openWithPrompt are queued until the iframe is ready, preventing race conditions.

YakProvider Props

PropDescription
appIdYak application identifier
getConfigAsync function returning chat config (default: fetches from /api/yak)
onToolCallAsync function executing tool calls (default: POSTs to /api/yak)
themeLauncher + panel styling
onRedirectCustom navigation handler

YakWidget Props

PropDescription
iframeClassNameCSS class for the iframe
triggerLabelButton 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 structureCommand
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.ts

Use 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" },
  ],
});

On this page