npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@yak-io/nextjs

v0.3.0

Published

Next.js SDK for embedding yak chatbot with route manifest generation

Downloads

92

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/javascript exposes the framework-agnostic client logic (iframe communication, tool execution) and server utilities (createYakHandler, RouteSource, ToolSource, etc.).
  • @yak-io/react exposes the React provider/widget and hooks.
  • @yak-io/nextjs re-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/nextjs

The 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-manifest

Use 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.ts

Route 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

  1. Add a prebuild script to generate the manifest before Next.js builds:
{
  "scripts": {
    "prebuild": "yak-nextjs generate-manifest",
    "build": "next build"
  }
}
  1. 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, extracting title and description from static metadata exports.
  • loadRouteManifest(path?: string) – load a pre-built JSON manifest via filesystem read, returns null if not found.
  • loadRoutes(path?: string) – load routes from a manifest, throws with helpful error if not found.
  • createNextYakHandler(config) – unified GET + POST handler. When routes/getRoutes are omitted it auto-scans ./src/app (override with appDir). 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/client continue to work, but the actual implementation lives in @yak-io/react.
  • createNextYakHandler auto-scans ./src/app and ./src/pages; configure via appDir, pagesDir, or regex-based routeFilter.
  • createNextYakHandler accepts routes/tools arrays. Legacy getRoutes/getTools/executeTool still work and are normalized internally.
  • For custom adapters prefer using @yak-io/javascript/server types so your code can be reused outside Next.js.

License

Proprietary - see LICENSE file