StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Konsultacje

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

RealizacjeO mnieBlog
Porozmawiajmy
PL
EN

Nowoczesne strony internetowe dla firm, które myślą odważnie.

Przewiń do góry

Nazwa

StriveLab Maciej Sala

NIP

6772218995

REGON

524008527

E-mail

contact@strivelab.pl

Usługi główne
  • Tworzenie stron internetowych
  • Strony internetowe Next.js
  • Strony internetowe Astro
  • Strony internetowe React
Inne usługi
  • Usługi
  • Audyt SEO i Performance
  • Testy automatyczne i QA
  • Konsultacje Produktowe
  • Automatyzacja Procesów AI
  • Aplikacje webowe Next.js
  • Współpraca ciągła
Strony
  • O mnie
  • Usługi
  • Realizacje
  • Blog

© 2026 StriveLab.pl

Polityka prywatności
Next.jsAI

RAG w Next.js — budujemy inteligentną bazę wiedzy z AI

Twoje dane + LLM = baza wiedzy, która odpowiada na pytania. Jak zbudować RAG w Next.js z pgvector i wyszukiwaniem semantycznym?

OpublikujLinkedInFacebookWyślij
Autor
Maciej Sala
Opublikowano
10 kwietnia 2026 16:10
Czytanie
4 min czytania
Aktualizacja
31 maja 2026 08:00

Modele językowe pokroju ChatGPT czy Claude potrafią być niesamowicie pożyteczne, ale nie mają pojęcia, co kryje się w Twojej wewnętrznej dokumentacji, bazie wiedzy czy FAQ. Zapytane o szczegóły, których nie znają, zaczynają halucynować. RAG rozwiązuje ten problem: zanim model odpowie, podsuwasz mu właściwe fragmenty Twoich danych i mówisz wprost: „Odpowiadaj na podstawie tego, a nie z pamięci". Ten artykuł pokazuje, jak zbudować taki system w Next.js — krok po kroku, od cięcia tekstu po gotowy chat.

Artykuł w skrócie

  • RAG = wyszukiwanie po znaczeniu + generowanie odpowiedzi — model dostaje zakaz zmyślania bez kontekstu pobranego z Twojej bazy wiedzy.
  • Pipeline ma sześć etapów — tnij tekst na fragmenty → zamień na embeddingi → zapisz w bazie wektorowej → wyszukaj po sensie → doklej kontekst do promptu → streamuj odpowiedź LLM do klienta.
  • Sprawdzony stack na start — Next.js (front + backend), Vercel AI SDK jako spoiwo, OpenAI Embeddings i pgvector przez Supabase jako tania baza wektorowa.
  • RAG zwykle bije fine-tuning — fine-tuning zostaw na ton i styl, a do wiedzy firmowej używaj RAG: tańszy, transparentny i aktualizowalny w sekundę.
  • Koszt zdominowany przez model odpowiadający — same embeddingi to grosze; przy ~10 tys. pytań miesięcznie rachunek za GPT-4o-mini lub Haiku to orientacyjnie 10–100 USD.
  • Chunki 400–600 tokenów na start — z overlapem ~50 tokenów to bezpieczny domyślny przedział dla dokumentacji i FAQ.

Retrieval-Augmented Generation (RAG) to architektura aplikacji AI, w której LLM dostaje kontekst pobrany z zewnętrznej bazy wiedzy (wektorowej) zamiast polegać tylko na wiedzy z pretrainingu. Ogranicza halucynacje, pozwala dodać aktualne i prywatne dane bez fine-tuningu. łata największą bolączkę potężnych modeli językowych: nie znają Twojej własnej bazy wiedzy. Zapytany o szczegół z firmowej dokumentacji, model bez kontekstu zacznie zgadywać. RAG zamienia to w kontrolowany proces — najpierw szukasz właściwego fragmentu, potem każesz modelowi odpowiedzieć wyłącznie na jego podstawie.

Zanim przejdziemy do implementacji — warto wiedzieć, że RAG nie zawsze wymaga pisania kodu. Google NotebookLM to gotowy produkt zbudowany na tej samej zasadzie: wgrywasz dokumenty, a przy każdym pytaniu system sam odnajduje odpowiednie fragmenty i podaje je modelowi jako kontekst. Różnica polega na tym, że w NotebookLM nie masz wglądu w to, jak działa ten proces — nie możesz zmienić sposobu cięcia tekstu, wybrać innego modelu ani wpiąć tego w swoją aplikację. Ten artykuł jest dla sytuacji, gdy potrzebujesz RAG wewnątrz własnego produktu, a nie jako osobne narzędzie.

  • Tnij długie teksty na fragmenty po ok. 400–800 tokenów.
  • Zamień fragmenty na Embeddingi to wektory liczbowe reprezentujące znaczenie semantyczne tekstu — teksty o podobnym znaczeniu mają wektory blisko siebie w przestrzeni wielowymiarowej. (np. modelem OpenAI).

  • Zapisz wektory w bazie danych (np. pgvector na start).

  • Przy zapytaniu użytkownika wyszukaj najbliższe fragmenty.

  • Sklej znalezione fragmenty w prompt z pytaniem i podaj taniemu LLM (np. Claude Haiku 4.5).

  • Streamuj odpowiedź do klienta przez Vercel AI SDK.
Info

Jeśli planujesz zbudować asystenta opartego na własnej bazie wiedzy firmy, zacznij od RAG, nie od Fine-tuning to dodatkowy trening modelu językowego na własnym zestawie danych, dzięki czemu model uczy się specyficznego tonu, stylu lub słownictwa dziedzinowego. W odróżnieniu od RAG nie pobiera wiedzy w czasie zapytania — wypala ją trwale w wagach modelu. Jest droższy, wolniejszy w aktualizacji i mniej transparentny niż RAG, dlatego stosuje się go głównie do kształtowania sposobu odpowiedzi, nie do przekazywania faktów., który jest droższy, wolniejszy do aktualizacji i nieprzejrzysty — nie widzisz, skąd model wziął odpowiedź. RAG daje świeże dane, jest tańszy i posiada pełną kontrolę nad źródłem każdej odpowiedzi.

Architektura RAG — jak płynie zapytanie

Code
Użytkownik pyta: "Jak zresetować hasło w firmowym systemie?"
       ↓
1. Zapytanie zamieniamy na wektor (embedding) przez API OpenAI — ciąg 1536 liczb
       ↓
2. Szukamy w bazie najbliższych fragmentów (np. wpis 'reset-hasla.mdx')
       ↓
3. Doklejamy znalezione fragmenty jako kontekst do promptu i wysyłamy do LLM
       ↓
4. Model odpowiada na podstawie kontekstu i streamuje wynik do chatu

Jakie narzędzia wziąć

  • Next.js — front i backend (API routes) w jednym projekcie.
  • Vercel AI SDK — spoiwo do strumieniowania odpowiedzi do przeglądarki, bez ręcznego zarządzania połączeniem.
  • OpenAI Embeddings — tanie i skuteczne generowanie wektorów z tekstu.
  • Baza wektorowa — Supabase z pgvector na MVP; Upstash Vector albo Pinecone przy większym ruchu produkcyjnym.
  • Zod — walidacja i typowanie danych wejściowych.

Etap 1: cięcie i obróbka danych

Długich artykułów nie wciśniesz w całości do prompt, dlatego tekst tniemy na mniejsze fragmenty (chunki):

Code
// lib/rag/chunker.ts
interface Chunk {
  content: string
  metadata: {
    source: string
    title: string
    chunkIndex: number
  }
}
 
export function chunkText(
  text: string,
  source: string,
  title: string,
  chunkSize: number = 500, // domyślnie ok. 500 słów na fragment,
  overlap: number = 100, // nachodzenie fragmentów, by nie ucinać kontekstu w pół zdania,
): Chunk[] {
  const words = text.split(/\s+/)
  const chunks: Chunk[] = []
  let i = 0
  let chunkIndex = 0
 
  while (i < words.length) {
    const chunk = words.slice(i, i + chunkSize).join(' ')
    chunks.push({
      content: chunk,
      metadata: { source, title, chunkIndex },
    })
    i += chunkSize - overlap
    chunkIndex++
  }
 
  return chunks
}

Etap 2: generowanie embeddingów

Code
// lib/rag/embeddings.ts
import { openai } from '@ai-sdk/openai'
import { embed, embedMany } from 'ai'
 
export async function generateEmbedding(text: string): Promise<number[]> {
  const { embedding } = await embed({
    model: openai.embedding('text-embedding-3-small'), // tani model w zupełności wystarcza do bazy wiedzy
    value: text,
  })
  return embedding
}
 
export async function generateEmbeddings(texts: string[]): Promise<number[][]> {
  const { embeddings } = await embedMany({
    model: openai.embedding('text-embedding-3-small'),
    values: texts,
  })
  return embeddings
}

Etap 3: baza wektorowa w Supabase

Baza musi umieć przechowywać i przeszukiwać wektory. Supabase robi to przez rozszerzenie vector (pgvector):

Code
-- Uruchom w edytorze SQL w Supabase:
create extension if not exists vector;
 
create table documents (
  id bigserial primary key,
  content text not null,
  metadata jsonb,
  embedding vector(1536) -- wymiar zgodny z text-embedding-3-small
);
 
-- Indeks ivfflat przyspiesza wyszukiwanie po podobieństwie
create index on documents using ivfflat (embedding vector_cosine_ops)
  with (lists = 100);
 
-- Funkcja zwracająca najbliższe fragmenty dla zapytania
create or replace function match_documents(
  query_embedding vector(1536),
  match_threshold float default 0.7,
  match_count int default 5
)
returns table (
  id bigint,
  content text,
  metadata jsonb,
  similarity float
)
language sql stable
as $$
  select
    id,
    content,
    metadata,
    1 - (embedding <=> query_embedding) as similarity
  from documents
  where 1 - (embedding <=> query_embedding) > match_threshold
  order by embedding <=> query_embedding
  limit match_count;
$$;

Etap 4: indeksowanie dokumentów

Skrypt, który raz przepuszcza Twoje treści przez chunker i embeddingi, a wynik zapisuje do bazy:

Code
// scripts/index-documents.ts — uruchamiasz raz, by zasilić bazę
import { chunkText } from '@/lib/rag/chunker'
import { generateEmbeddings } from '@/lib/rag/embeddings'
import { createClient } from '@supabase/supabase-js'
import fs from 'fs'
import path from 'path'
 
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!,
)
 
async function indexDocuments() {
  // Bierzemy gotowe artykuły .mdx z dysku
  const articlesDir = path.join(process.cwd(), 'content/blog')
  const files = fs.readdirSync(articlesDir).filter((f) => f.endsWith('.mdx'))
 
  for (const file of files) {
    const content = fs.readFileSync(path.join(articlesDir, file), 'utf-8')
    const title = file.replace('.mdx', '')
 
    // Tniemy na fragmenty
    const chunks = chunkText(content, file, title)
    console.log(`Plik ${file}: ${chunks.length} fragmentów.`)
 
    // Generujemy embeddingi dla wszystkich fragmentów naraz
    const embeddings = await generateEmbeddings(chunks.map((c) => c.content))
 
    // Zapisujemy do bazy
    const records = chunks.map((chunk, i) => ({
      content: chunk.content,
      metadata: chunk.metadata,
      embedding: embeddings[i],
    }))
 
    const { error } = await supabase.from('documents').insert(records)
    if (error) console.error(`Błąd indeksowania pliku ${file}:`, error)
  }
 
  console.log('Baza wektorowa gotowa.')
}
 
indexDocuments()

Etap 5: endpoint API ze streamingiem

Główny kontroler: przyjmuje pytanie, znajduje kontekst i streamuje odpowiedź modelu:

Code
// app/api/rag/route.ts
import { openai } from '@ai-sdk/openai'
import { convertToModelMessages, streamText, type UIMessage } from 'ai'
import { generateEmbedding } from '@/lib/rag/embeddings'
import { createClient } from '@supabase/supabase-js'
 
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_ROLE_KEY!,
)
 
export async function POST(req: Request) {
  const { messages }: { messages: UIMessage[] } = await req.json()
  const lastMessage = messages.at(-1)
  const lastMessageText =
    lastMessage?.parts
      .filter((part) => part.type === 'text')
      .map((part) => part.text)
      .join('\n') ?? ''
 
  if (!lastMessageText.trim()) {
    return Response.json({ error: 'Puste zapytanie.' }, { status: 400 })
  }
 
  // 1. Zamieniamy pytanie użytkownika na wektor
  const queryEmbedding = await generateEmbedding(lastMessageText)
 
  // 2. Szukamy najbliższych fragmentów w bazie
  const { data: documents } = await supabase.rpc('match_documents', {
    query_embedding: queryEmbedding,
    match_threshold: 0.7,
    match_count: 5,
  })
 
  // 3. Sklejamy znalezione fragmenty w kontekst
  const context = documents
    ?.map((doc: any) => `[Źródło: ${doc.metadata.title}]\n${doc.content}`)
    .join('\n\n---\n\n')
 
  // 4. Każemy modelowi odpowiedzieć wyłącznie na podstawie kontekstu
  const result = streamText({
    model: openai('gpt-4o'),
    system: `Jesteś asystentem bazy wiedzy StriveLab. Odpowiadaj wyłącznie na podstawie kontekstu poniżej. Jeśli odpowiedzi nie ma w kontekście, napisz wprost, że nie znalazłeś informacji. Nie zmyślaj.
 
KONTEKST:
${context || 'Brak pasującego kontekstu w bazie.'}`,
    messages: await convertToModelMessages(messages),
  })
 
  return result.toUIMessageStreamResponse()
}

Etap 6: front — interfejs chatu

Code
'use client'
 
import { useState } from 'react'
import { DefaultChatTransport } from 'ai'
import { useChat } from '@ai-sdk/react'
 
export default function KnowledgeBase() {
  const [input, setInput] = useState('')
 
  // Vercel AI SDK obsługuje połączenie z endpointem RAG
  const { messages, sendMessage, status } = useChat({
    transport: new DefaultChatTransport({ api: '/api/rag' }),
  })
  const isLoading = status === 'submitted' || status === 'streaming'
 
  function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault()
    if (!input.trim()) return
 
    sendMessage({ text: input })
    setInput('')
  }
 
  return (
    <div className="mx-auto max-w-2xl p-6">
      <h1 className="mb-2 text-2xl font-bold">
        Zapytaj asystenta bazy wiedzy StriveLab
      </h1>
      <p className="mb-6 text-gray-500">
        Pytaj o technologie, SEO i usługi opisane na stronie.
      </p>
 
      <div className="mb-6 min-h-[200px] space-y-4">
        {messages.map((m) => (
          <div
            key={m.id}
            className={`rounded-lg p-4 ${
              m.role === 'user' ? 'ml-8 bg-blue-50' : 'mr-8 bg-gray-50'
            }`}
          >
            {m.parts.map((part, index) =>
              part.type === 'text' ? (
                <p key={`${m.id}-${index}`} className="whitespace-pre-wrap">
                  {part.text}
                </p>
              ) : null,
            )}
          </div>
        ))}
      </div>
 
      <form onSubmit={handleSubmit} className="flex gap-2">
        <input
          value={input}
          onChange={(event) => setInput(event.target.value)}
          placeholder="O czym jest ta strona w kontekście SEO?"
          className="flex-1 rounded-lg border px-4 py-3"
        />
        <button
          type="submit"
          disabled={isLoading}
          className="rounded-lg bg-blue-600 px-6 py-3 text-white disabled:opacity-50"
        >
          Zapytaj
        </button>
      </form>
    </div>
  )
}

Werdykt Labu

RAG to najszybsza i najtańsza droga, by model językowy odpowiadał na podstawie Twojej wiedzy firmowej i działał bez halucynacji. Cały sekret to sześć etapów: pociąć tekst, zamienić na embeddingi, zapisać w bazie wektorowej, wyszukać po znaczeniu, dokleić kontekst do promptu i streamować odpowiedź. W Next.js składasz to z gotowych klocków — Vercel AI SDK, OpenAI Embeddings i pgvector przez Supabase — bez budowania niczego od zera.

Najważniejsza decyzja jest strategiczna: dla wiedzy firmowej wybieraj RAG, nie fine-tuning. RAG jest tańszy, transparentny (widzisz źródło każdej odpowiedzi) i aktualizujesz go w sekundę, dodając lub usuwając dokument. Fine-tuning zostaw na ton i styl, nie na fakty. Jeśli chcesz najpierw zobaczyć, jak RAG działa w praktyce bez pisania kodu, zacznij od Google NotebookLM — to dobry punkt odniesienia przed budowaniem własnego systemu.

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.
Automatyzacja AI
  • Architektura RAG — jak płynie zapytanie1 min
  • Jakie narzędzia wziąć1 min
  • Etap 1: cięcie i obróbka danych1 min
  • Etap 2: generowanie embeddingów1 min
  • Etap 3: baza wektorowa w Supabase1 min
  • Etap 4: indeksowanie dokumentów1 min
  • Etap 5: endpoint API ze streamingiem1 min
  • Etap 6: front — interfejs chatu1 min
  • Werdykt Labu1 min

Często zadawane pytania

ŹródłaZweryfikowano: 31 maja 2026

Dokumentacja Vercel AI SDK, OpenAI i pgvector zweryfikowana podczas redakcji artykułu.

  • Vercel AI SDK — Documentation - Vercel AI SDK — embed & embedMany - OpenAI — Embeddings - Supabase — pgvector & AI

Seria

AI dla developerów 2026
Część 3 / 3
  1. 1MCP (Model Context Protocol) — jak zbudować serwer MCP w TypeScript i podłączyć AI agentów do swojej aplikacji
  2. 2Vercel AI SDK — streaming chatbot w Next.js w 30 minut
  3. RAG w Next.js — budujemy inteligentną bazę wiedzy z AI
Maciej Sala

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwija własne projekty side-projecty.

Moje artykułyWięcej o mnie

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Vercel AI SDK — streaming chatbot w Next.js w 30 minut
Vercel AI SDK — streaming chatbot w Next.js w 30 minut

Streaming chatbot z Vercel AI SDK w Next.js — od zera do działającego UI z OpenAI/Claude w 30 minut. Bez zbędnego boilerplate.

Maciej Sala

Maciej Sala

Founder Strivelab

10 kwietnia 2026
Techniczne SEO dla agentów AI — robots.txt, GPTBot, ClaudeBot i dostępność danych dla RAG
Techniczne SEO dla agentów AI — robots.txt, GPTBot, ClaudeBot i dostępność danych dla RAG

GPTBot, ClaudeBot, PerplexityBot — każdy chce indeksować Twoją stronę inaczej. Jak sterować dostępem AI i nie zablokować RAG przez przypadek?

Maciej Sala

Maciej Sala

Founder Strivelab

30 maja 2026
Supabase + Next.js — uwierzytelnianie, baza, storage i realtime w jednym stacku
Supabase + Next.js — uwierzytelnianie, baza, storage i realtime w jednym stacku

Auth, baza, storage i realtime w jednym stacku — Supabase + Next.js App Router w 2026 z @supabase/ssr, bez przestarzałych Auth Helpers.

Maciej Sala

Maciej Sala

Founder Strivelab

10 kwietnia 2026
Poprzedni wpisVercel AI SDK — streaming chatbot w Next.js w 30 minutStreaming chatbot z Vercel AI SDK w Next.js — od zera do działającego UI z OpenAI/Claude w 30 minut. Bez zbędnego boilerplate.
Maciej Sala

Maciej Sala

Founder Strivelab

10 kwietnia 2026
Następny wpisNext.js vs WordPress w 2026 — kiedy polecam jedno, a kiedy drugieNext.js vs WordPress w 2026 — bez clickbaitu. Kiedy WordPress nadal wygrywa, kiedy przegrywa i co rzeczywiście decyduje o wyborze stosu?
Maciej Sala

Maciej Sala

Founder Strivelab

10 kwietnia 2026