SDK & Integration

Vue and Nuxt integration guide

Updated 87 views

The SDK is framework-agnostic — it is plain TypeScript over fetch, so it works in Vue exactly as it does anywhere else.

Vue 3 with Vite

TypeScript
// src/lib/kookee.ts
import { Kookee } from '@kookee/sdk';

export const kookee = new Kookee({
  apiKey: import.meta.env.VITE_KOOKEE_API_KEY,
});

Vite only exposes VITE_-prefixed variables to client code, and anything exposed that way is visible in your bundle. That is fine for the Kookee API key: it reads only published content, and its only writes are reactions, chat messages, consent records and the site widget's feedback messages, which stay private until your team reviews them — see API keys and security.

A composable

TypeScript
// src/composables/useBlogPosts.ts
import { ref, onMounted } from 'vue';
import { kookee } from '@/lib/kookee';
import type { BlogEntryListItem } from '@kookee/sdk';

export function useBlogPosts(limit = 10) {
  const posts = ref<BlogEntryListItem[]>([]);
  const loading = ref(true);
  const error = ref<Error | null>(null);

  onMounted(async () => {
    try {
      const response = await kookee.blog.list({ limit });
      posts.value = response.data;
    } catch (e) {
      error.value = e as Error;
    } finally {
      loading.value = false;
    }
  });

  return { posts, loading, error };
}
vue
<script setup lang="ts">
import { useBlogPosts } from '@/composables/useBlogPosts';

const { posts, loading, error } = useBlogPosts();
</script>

<template>
  <p v-if="loading">Loading…</p>
  <p v-else-if="error">Could not load posts.</p>
  <ul v-else>
    <li v-for="post in posts" :key="post.id">
      <RouterLink :to="`/blog/${post.slug}`">{{ post.title }}</RouterLink>
    </li>
  </ul>
</template>

Always render an error branch. A client-side fetch can fail on a flaky connection or a rate limit, and a component that only handles the happy path shows an empty list instead of telling the user anything.

Rendering content

vue
<script setup lang="ts">
const post = await kookee.blog.getBySlug(slug);
</script>

<template>
  <article>
    <h1>{{ post.title }}</h1>
    <div class="kookee-entry-content" v-html="post.contentHtml" />
  </article>
</template>

Import the code block stylesheet once in your entry file:

TypeScript
// src/main.ts
import '@kookee/sdk/styles/content.css';

Nuxt

Nuxt can fetch on the server, which is usually better — one request serves every visitor to a page, and the work happens once instead of in every browser.

Put the key in runtimeConfig:

TypeScript
// nuxt.config.ts
export default defineNuxtConfig({
  runtimeConfig: {
    kookeeApiKey: process.env.KOOKEE_API_KEY, // server-only
  },
});

Anything under runtimeConfig (rather than runtimeConfig.public) never reaches the client bundle. With a Kookee key that is a convenience rather than a security requirement, but it keeps server-side configuration in one place.

TypeScript
// server/utils/kookee.ts
import { Kookee } from '@kookee/sdk';

export function useKookee() {
  const config = useRuntimeConfig();
  return new Kookee({ apiKey: config.kookeeApiKey });
}

A server route:

TypeScript
// server/api/posts.get.ts
export default defineEventHandler(async () => {
  const kookee = useKookee();
  return await kookee.blog.list({ limit: 10 });
});

And the page:

vue
<script setup lang="ts">
const { data } = await useAsyncData('posts', () => $fetch('/api/posts'));
</script>

<template>
  <ul>
    <li v-for="post in data?.data" :key="post.id">{{ post.title }}</li>
  </ul>
</template>

useAsyncData runs on the server during SSR and hydrates on the client, so the payload is not fetched twice.

Caching in Nuxt

Wrap a server route in defineCachedEventHandler to avoid an API call per request:

TypeScript
export default defineCachedEventHandler(
  async () => {
    const kookee = useKookee();
    return await kookee.blog.list({ limit: 10 });
  },
  { maxAge: 300 }, // 5 minutes
);

The chat widget

KookeeChatWidget is a React component, but you do not need React to use the chat. The script widget works on any website, Vue and Nuxt included:

HTML
<script async src="https://kookee.dev/chat/latest.js" data-api-key="YOUR_API_KEY"></script>

In a Vue SPA, put that tag in index.html. In Nuxt, add it through app.head.script in nuxt.config.ts. Either way, add your site's domain to the project's Allowed Origins first — see Install the site widget on your website.

To open or close the panel from your own components, use the window.KookeeChat API — see Control the chat widget from JavaScript.

If you would rather build your own search UI instead, use kookee.help.search() directly — see Building a help center with search and AI chat for the response shape, including matchedChunk.

Was this article helpful?