Skip to main content
Integrate Moss semantic search into a Next.js application using Server Actions. This pattern keeps your API keys secure on the server while giving your frontend sub-10ms search results.
Note: For the complete demo application, see the Next.js example.

Why use Moss with Next.js?

Server Actions provide a clean boundary between client and server code. Moss runs server-side to keep credentials secure, while the client gets fast, relevant search results without managing API infrastructure or exposing keys.

Required tools

Integration guide

1

Installation

npm install @inferedge/moss
2

Environment setup

Create a .env.local file in your project root with your Moss credentials.
.env.local
MOSS_PROJECT_ID=your_project_id
MOSS_PROJECT_KEY=your_project_key
MOSS_INDEX_NAME=your_index_name
3

Create a Server Action

Create a Server Action that initializes the Moss client, loads the index, and runs queries.
app/actions.ts
"use server";

import { MossClient } from "@inferedge/moss";

const client = new MossClient(
  process.env.MOSS_PROJECT_ID!,
  process.env.MOSS_PROJECT_KEY!,
);

const indexName = process.env.MOSS_INDEX_NAME!;
const indexReady = client.loadIndex(indexName);

export async function searchMoss(query: string) {
  await indexReady;

  const results = await client.query(indexName, query, { topK: 5 });

  return results.docs.map((doc) => ({
    id: doc.id,
    text: doc.text,
    score: doc.score,
    metadata: doc.metadata,
  }));
}
4

Call from a client component

Call the Server Action from any client component to display search results.
app/page.tsx
"use client";

import { useState, useTransition } from "react";
import { searchMoss } from "./actions";

export default function SearchPage() {
  const [query, setQuery] = useState("");
  const [results, setResults] = useState([]);
  const [isPending, startTransition] = useTransition();

  function handleSearch() {
    if (!query.trim()) return;
    startTransition(async () => {
      const data = await searchMoss(query);
      setResults(data);
    });
  }

  return (
    <main>
      <input
        value={query}
        onChange={(e) => setQuery(e.target.value)}
        onKeyDown={(e) => e.key === "Enter" && handleSearch()}
        placeholder="Search..."
      />
      <button onClick={handleSearch} disabled={isPending}>
        {isPending ? "Searching..." : "Search"}
      </button>
      {results.map((r) => (
        <div key={r.id}>
          <p>{r.text}</p>
          <span>Score: {(r.score * 100).toFixed(1)}%</span>
        </div>
      ))}
    </main>
  );
}