Skip to main content
Introducing packages.sweber.dev
Documentation menuRecipes

Recipes

Short setups for Next.js, Astro, VitePress, Docusaurus, SvelteKit, Vue and plain HTML.

Every recipe has the same two steps: build the index into a folder that the site serves as /cosine/, then add the search field. Install first:

npm i @sweberdev/cosine @huggingface/transformers

The index URL is always an absolute path such as /cosine/cosine-index.json. If your site lives below a base path, include it, see Troubleshooting.

Next.js

With a static export (output: "export"), index the exported HTML after the build:

package.json

{
  "scripts": {
    "build": "next build && cosine build out --out out/cosine"
  }
}

components/search.tsx

"use client";

import { CosineSearch } from "@sweberdev/cosine-react";
import { useRouter } from "next/navigation";

export function Search() {
  const router = useRouter();
  return (
    <CosineSearch
      index="/cosine/cosine-index.json"
      onSelect={(result) => {
        router.push(result.chunk.url);
        return false;
      }}
    />
  );
}

Install @sweberdev/cosine-react as well, see React. For sites that are not exported, cosine-next (Cosine Pro) reads the prerendered pages in .next and caches the index between builds.

Astro

package.json

{
  "scripts": {
    "build": "astro build && cosine build dist --out dist/cosine"
  }
}

src/components/Search.astro

<cosine-search index="/cosine/cosine-index.json"></cosine-search>

<script>
  import "@sweberdev/cosine/element";
</script>

The Pro plugin cosine-vite builds the index inside the Astro build and serves it in dev.

VitePress

Index the Markdown sources. Folders that start with a dot, such as .vitepress, are skipped. Set cleanUrls: true in the VitePress config, so the page URLs match.

package.json

{
  "scripts": {
    "docs:build": "cosine build docs --out docs/public/cosine && vitepress build docs"
  }
}

docs/.vitepress/theme/index.ts

import DefaultTheme from "vitepress/theme";
import { h } from "vue";

export default {
  extends: DefaultTheme,
  Layout() {
    return h(DefaultTheme.Layout, null, {
      "nav-bar-content-before": () => h("cosine-search", { index: "/cosine/cosine-index.json" }),
    });
  },
  async enhanceApp() {
    if (!import.meta.env.SSR) await import("@sweberdev/cosine/element");
  },
};

If the site has a base, build with --base-url /base/ and use /base/cosine/cosine-index.json as the index.

Docusaurus

package.json

{
  "scripts": {
    "build": "docusaurus build && cosine build build --out build/cosine"
  }
}

Swizzle the search bar and render the element on the client only:

src/theme/SearchBar/index.js

import React from "react";
import BrowserOnly from "@docusaurus/BrowserOnly";

export default function SearchBar() {
  return (
    <BrowserOnly>
      {() => {
        require("@sweberdev/cosine/element");
        return <cosine-search index="/cosine/cosine-index.json" />;
      }}
    </BrowserOnly>
  );
}

During docusaurus start the index does not exist yet. Run cosine build build --out static/cosine once after a build to try the search in dev.

SvelteKit

With adapter-static:

package.json

{
  "scripts": {
    "build": "vite build && cosine build build --out build/cosine"
  }
}

src/routes/+layout.svelte

<script>
  import { onMount } from "svelte";

  onMount(() => import("@sweberdev/cosine/element"));
</script>

<cosine-search index="/cosine/cosine-index.json"></cosine-search>
<slot />

Vue (Vite)

Build the index from your Markdown before the Vite build and tell Vue that cosine-search is a custom element:

package.json

{
  "scripts": {
    "prebuild": "cosine build content --out public/cosine --base-url /docs"
  }
}

vite.config.ts

import vue from "@vitejs/plugin-vue";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    vue({ template: { compilerOptions: { isCustomElement: (tag) => tag === "cosine-search" } } }),
  ],
});

src/App.vue

<script setup lang="ts">
import "@sweberdev/cosine/element";

function onSelect(event: CustomEvent) {
  event.preventDefault(); // handle navigation with your router
  console.log(event.detail.chunk.url);
}
</script>

<template>
  <cosine-search index="/cosine/cosine-index.json" @cosine-select="onSelect" />
</template>

Plain HTML

npx cosine build site --out site/cosine
<script type="module">
  import "@sweberdev/cosine/element";
</script>
<cosine-search index="/cosine/cosine-index.json"></cosine-search>

The bare specifier @sweberdev/cosine/element needs a bundler (Vite, esbuild, webpack) or an import map that points to a copy of the package you serve yourself.