Other Frameworks
Astro
Astro sites use the @yak-io/react package as a React island for the client-side widget and @yak-io/javascript for server handlers.
Installation
npm install @yak-io/react @yak-io/javascript react react-dom
npx astro add reactpnpm add @yak-io/react @yak-io/javascript react react-dom
pnpm astro add reactyarn add @yak-io/react @yak-io/javascript react react-dom
yarn astro add reactbun add @yak-io/react @yak-io/javascript react react-dom
bunx astro add reactClient Component
Create a React component for the Yak widget:
// src/components/YakChat.tsx
import { YakProvider, YakWidget } from "@yak-io/react";
interface YakChatProps {
appId: string;
}
export function YakChat({ appId }: YakChatProps) {
return (
<YakProvider
appId={appId}
getConfig={async () => {
const res = await fetch("/api/yak");
return res.json();
}}
onToolCall={async (name, args) => {
const res = await fetch("/api/yak", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name, args }),
});
const data = await res.json();
if (!data.ok) throw new Error(data.error);
return data.result;
}}
onRedirect={(path) => window.location.href = path}
>
<YakWidget />
</YakProvider>
);
}Layout Integration
Add the component to your layout as a React island:
---
// src/layouts/Layout.astro
import { YakChat } from "../components/YakChat";
---
<html>
<head>
<title>{title}</title>
</head>
<body>
<slot />
<YakChat client:load appId={import.meta.env.PUBLIC_YAK_APP_ID} />
</body>
</html>The client:load directive ensures the widget loads on the client side immediately. Use client:idle for lower priority loading.
Server Handler
Create an API endpoint for Yak:
// src/pages/api/yak.ts
import type { APIRoute } from "astro";
import { createYakHandler } from "@yak-io/javascript/server";
const { GET: yakGet, POST: yakPost } = createYakHandler({
routes: [
{ path: "/", title: "Home" },
{ path: "/about", title: "About" },
{ path: "/blog", title: "Blog" },
],
});
export const GET: APIRoute = async ({ request }) => {
return yakGet(request);
};
export const POST: APIRoute = async ({ request }) => {
return yakPost(request);
};Dynamic Routes from Content Collections
Pull routes from Astro content collections:
// src/pages/api/yak.ts
import { getCollection } from "astro:content";
import { createYakHandler } from "@yak-io/javascript/server";
async function getRoutes() {
const posts = await getCollection("blog");
return [
{ path: "/", title: "Home" },
{ path: "/blog", title: "Blog" },
...posts.map((post) => ({
path: `/blog/${post.slug}`,
title: post.data.title,
description: post.data.description,
})),
];
}
export const GET: APIRoute = async ({ request }) => {
const routes = await getRoutes();
const { GET } = createYakHandler({ routes });
return GET(request);
};Adding Tools
Connect to your data sources:
// src/pages/api/yak.ts
const contentTools = {
id: "content",
getTools: async () => [
{
name: "content.searchPosts",
description: "Search blog posts by keyword",
input_schema: {
type: "object",
properties: {
query: { type: "string" },
limit: { type: "number", default: 5 },
},
required: ["query"],
},
},
],
executeTool: async (name: string, args: Record<string, unknown>) => {
if (name === "content.searchPosts") {
const posts = await getCollection("blog");
const query = (args.query as string).toLowerCase();
const limit = (args.limit as number) ?? 5;
return posts
.filter((post) =>
post.data.title.toLowerCase().includes(query) ||
post.body.toLowerCase().includes(query)
)
.slice(0, limit)
.map((post) => ({
title: post.data.title,
slug: post.slug,
description: post.data.description,
}));
}
throw new Error(`Unknown tool: ${name}`);
},
};
const { GET, POST } = createYakHandler({
routes: [...],
tools: [contentTools],
});Environment Variables
Add your app ID to .env:
PUBLIC_YAK_APP_ID=yak_app_123Use PUBLIC_ prefix for environment variables that need to be accessible on the client side.