RAG Vector Search Starter for Next.js
Overview
A Next.js 16 App Router starter for semantic search over your own documents. Embeddings are produced on-device by a MiniLM sentence-transformer through Transformers.js, and vectors are stored in an in-process index persisted to a JSON file, so it runs with no API key and no database. Both layers sit behind interfaces with a second implementation each: an OpenAI-compatible HTTP embedding provider (with configurable retry and backoff) and a Postgres/pgvector store with an included migration. Ships an ingestion pipeline (txt/md loader, recursive character chunker with overlap, batch embedding), validated /api/ingest and /api/search route handlers, a search UI with scores and highlighted matches, a CLI ingester, and sample documents. Both optional backends come with a harness so a buyer can exercise them without paying for anything: a mock OpenAI-compatible embeddings server that the provider's integration tests drive over real HTTP, and a docker-compose pgvector service behind a one-command `npm run verify:pgvector` round trip. The README states plainly which adapters have been executed and which have not.
Source preview
/**
* Greedily packs atoms up to `chunkSize`, then rewinds by `chunkOverlap`
* characters worth of whole atoms to seed the next chunk.
*/
function mergeAtoms(atoms: readonly string[], chunkSize: number, chunkOverlap: number): TextChunk[] {
const chunks: TextChunk[] = [];
let current: string[] = [];
let currentLength = 0;
let currentStart = 0;
let cursor = 0;
const emit = (): void => {
if (current.length === 0) return;
const raw = current.join('');
const chunk = trimToChunk(raw, currentStart, chunks.length);
if (chunk !== null) chunks.push(chunk);
};
for (const atom of atoms) {
if (currentLength > 0 && currentLength + atom.length > chunkSize) {
emit();
// Rewind: keep whole trailing atoms until the overlap budget is spent.
const tail: string[] = [];
let tailLength = 0Excludes tax, added at checkout where it applies.