Busca semântica no Next.js com embeddings OpenAI e Redis Vector — passo a passo
Por John Pires · 19 de março de 2026

Por que aprender isso em 2026
Contexto técnico e tendência
Busca semântica deixou de ser experimento e virou requisito em produtos que precisam recuperar informação relevante independentemente da escolha exata de palavras do usuário. Em 2026, modelos de linguagem e embeddings são padrão na arquitetura de pesquisa de documentos, chatops e assistentes de código. Aprender a integrar embeddings com ferramentas como Redis Vector e frameworks como Next.js coloca você em posição de entregar recursos de alto valor sem depender de serviços caros para cada consulta.
Analogia simples para entender o fluxo
Imagine uma biblioteca onde cada livro tem um resumo numérico que captura o sentido do texto, em vez de um índice alfabético. Quando o leitor faz uma pergunta, você traduz a pergunta para esse mesmo formato numérico e procura pelos resumos mais próximos. Embeddings fazem o resumo numérico e Redis Vector é o armário com capacidade de procurar por proximidade de resumo de forma muito rápida.
Resumo técnico: você gera embeddings com OpenAI, armazena vetores no Redis usando o módulo de vetores (Redisearch), e consulta com KNN para obter resultados semânticos. Vamos direto ao prático.
Configuracao do ambiente
Requisitos mínimos e versões
Use Node 18LTS ou superior. Next.js 14+ recomendado, mas exemplos funcionam com 13.x em app routes ou pages routes com pequenas adaptações. Redis precisa ser 7.0+ com Redisearch e suporte a vetores, ou use Redis Enterprise ou um serviço gerenciado que expose vetor search.
Instalação de dependencias
Instale dependencias essenciais: next, react, openai ou fetch, redis ou ioredis. Exemplo de instalação com npm:
npm init -y
npm install next react react-dom openai redis
O que acontece: isso prepara o projeto e traz a biblioteca oficial openai e um cliente redis de baixo nivel. Alternativas: redis-om para abstracoes, mas no tutorial vamos mostrar comando Redisearch direto para ficar explícito.
Variaveis de ambiente
Crie um arquivo .env.local na raiz do projeto com chaves seguras. Exemplo de variaveis:
OPENAI_API_KEY='coloque_sua_chave_aqui'
REDIS_URL='redis://:senha@localhost:6379'
Contexto: mantenha a chave do OpenAI no servidor. Nunca faça chamadas para gerar embeddings diretamente do cliente com a chave embutida.
Hello World progressivo
Primeiro erro comum: embedding no cliente
Erros que vejo no code review: colocar a chave do OpenAI no frontend para chamar embeddings, ou gerar embeddings no frontend e enviar vetores brutos ao servidor sem validação. Isso expõe a chave e aumenta custo e latencia desnecessariamente.
Dica: chaves de IA e criação de embeddings são operações sensiveis. Rode no servidor e aplique rate limit e caching.
Exemplo errado: frontend gerando embedding e enviando para Redis diretamente. Isso expõe a chave e transfere work desnecessario ao cliente.
// MAL - codigo do lado do cliente que NAO deve existir
async function embedAndStore(text) {
// chave embutida no bundle do cliente - inseguro
const r = await fetch('https://api.openai.com/v1/embeddings', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + process.env.OPENAI_API_KEY },
body: JSON.stringify({ model: 'text-embedding-3-small', input: text })
})
const json = await r.json()
const vector = json.data[0].embedding
await fetch('/api/store-vector', { method: 'POST', body: JSON.stringify({ text, vector }) })
}
O que acontece: chave exposta, usuário pode abusar da API, custo cresce e você perde controle dos embeddings criados.
Abordagem correta: gerar embeddings no servidor
Vamos criar uma rota Next.js que gera embedding com OpenAI e retorna um id. Esse endpoint roda no servidor e usa a chave do OpenAI guardada em variaveis de ambiente.
// pages/api/embed.js
import OpenAI from 'openai'
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
export default async function handler(req, res) {
const { text } = req.body
if (!text) return res.status(400).json({ error: 'text required' })
const resp = await openai.embeddings.create({ model: 'text-embedding-3-small', input: text })
const embedding = resp.data[0].embedding
return res.status(200).json({ embedding })
}
O que acontece: servidor chama OpenAI, chave fica segura. Agora você pode optar por armazenar o vetor no Redis aqui mesmo ou retornar para servidor que fará o armazenamento.
Mini-projeto pratico
Descrição do mini-projeto
Construiremos um index simples de notas. Fluxo: o servidor gera embedding ao salvar nota e persiste no Redis. Para busca, o servidor recebe a query, gera embedding e executa uma consulta KNN no Redis, retornando textos mais proximos. Tudo feito com Next.js API routes.
Criando o index vector no Redis
Contexto: antes de inserir vetores, crie um índice Redisearch configurado para vetores. Esse comando pode ser executado via redis-cli ou por codigo. Dimensão deve bater com o modelo de embedding.
// script para criar indice via node usando cliente redis
import { createClient } from 'redis'
async function createIndex() {
const client = createClient({ url: process.env.REDIS_URL })
await client.connect()
// assume model retorna 1536 dimensoes
try {
await client.sendCommand(['FT.CREATE', 'idx:notes', 'ON', 'HASH', 'PREFIX', '1', 'note:', 'SCHEMA', 'content', 'TEXT', 'vector', 'VECTOR', 'FLAT', '6', 'TYPE', 'FLOAT32', 'DIM', '1536', 'DISTANCE_METRIC', 'COSINE'])
console.log('index criado')
} catch (err) {
console.log('provavelmente indice ja existe', err.message)
}
await client.quit()
}
createIndex()
O que acontece: o indice idx:notes é criado com um campo vector para busca KNN. Em producao, escolha HNSW para maior escala e ajuste parametros conforme memoria. Erro comum: dimension diferente do vetor real causa falha na insercao.
Inserindo documentos com embeddings
Agora um endpoint que gera embedding e salva no Redis convertendo o vetor para formato binario exigido pelo Redis.
// pages/api/save-note.js
import OpenAI from 'openai'
import { createClient } from 'redis'
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
export default async function handler(req, res) {
const { id, content } = req.body
if (!id || !content) return res.status(400).json({ error: 'id and content required' })
const embResp = await openai.embeddings.create({ model: 'text-embedding-3-small', input: content })
const vector = embResp.data[0].embedding
const client = createClient({ url: process.env.REDIS_URL })
await client.connect()
// converter array de floats para Buffer little endian float32
const float32 = new Float32Array(vector)
const buffer = Buffer.from(float32.buffer)
await client.hSet('note:' + id, { content, vector: buffer })
await client.quit()
return res.status(200).json({ ok: true })
}
O que acontece: hSet armazena o texto e o vetor binario. Em consultas KNN, o Redis vai ler esse campo vector para calcular proximidade.
Consulta semantica com KNN
Agora o endpoint de busca: recebe query, gera embedding, e faz FT.SEARCH com KNN. O parse retorna os ids e scores.
// pages/api/search.js
import OpenAI from 'openai'
import { createClient } from 'redis'
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY })
export default async function handler(req, res) {
const { q, k = 5 } = req.body
if (!q) return res.status(400).json({ error: 'q required' })
const emb = await openai.embeddings.create({ model: 'text-embedding-3-small', input: q })
const vector = emb.data[0].embedding
const float32 = new Float32Array(vector)
const queryVector = Buffer.from(float32.buffer).toString('base64')
const client = createClient({ url: process.env.REDIS_URL })
await client.connect()
// FT.SEARCH idx:notes @vector:[KNN k $vec AS score] RETURN 2 content score PARAMS 2 vec <blob>
const query = ['FT.SEARCH', 'idx:notes', 'KNN', String(k), 'vector', 'BLOB', queryVector, 'RETURN', '2', 'content', 'vector']
// many redis clients nao suportam BLOB direto; aqui mostramos o que precisa acontecer em baixo nivel
const resp = await client.sendCommand(query)
// parse simplificado: resp vem alternando id, fields
const results = []
for (let i = 0; i < resp.length; i += 2) {
const id = resp[i]
const fields = resp[i + 1]
// fields e array alternando chave e valor
const obj = {}
for (let j = 0; j < fields.length; j += 2) obj[fields[j]] = fields[j + 1]
results.push({ id, content: obj.content })
}
await client.quit()
return res.status(200).json({ results })
}
O que acontece: o servidor retorna documentos semânticos similares. Ajuste retorno e score para atender sua interface.
Erros comuns e armadilhas
Lista de erros que surgem frequentemente em code review quando se trabalha com embeddings e Redis:
- Dimensao do vetor errada ao criar indice
- Armazenar vetores como strings JSON em vez de binario, causando busca KNN ineficiente ou falhas
- Expor a chave do OpenAI no cliente
- Ignorar normalizacao e metodos de distancia adequados ao criar o indice
- Consultar Redis sem limites ou sem paging, gerando uso excessivo de CPU
Exemplo errado de armazenamento: salvar vetor como JSON string e tentar usar FT.SEARCH no campo faz com que Redis nao reconheca como vetor.
// MAL - nao fazer isso
await client.hSet('note:' + id, { content, vector: JSON.stringify(vector) })
Corrija convertendo para Float32Array e Buffer como mostrado acima. Isso garante compatibilidade com o formato VECTOR do Redis.
Recursos para continuar estudando
Documentacao oficial e tracking de novos modelos
Leia a documentacao do OpenAI sobre embeddings e combinacoes de modelos, e a documentacao do Redis sobre RediSearch e vectordb. Esses recursos definem parametros de dimensao, metricas e exemplos de producao.
Como evoluir o mini-projeto
Depois de ter o pipeline basico, evolua para: versionamento de embeddings por modelo, usar HNSW para escalabilidade, armazenar metadados para filtros faceted, e criar pipelines de atualização incremental de embeddings com filas (ex: BullMQ).
Outra evolucao é integrar um reranker do tipo cross-encoder para refinar ordem dos candidatos retornados pelo vetor, reduzindo falsos positivos.
Conclusao pratica
Checklist pratico apos seguir o tutorial
Ao terminar: voce tera um endpoint que salva notas gerando embeddings no servidor, um indice Redis compatível com vetores, e um endpoint de busca que retorna resultados semanticos. Verifique se: variaveis de ambiente estao configuradas, indices tem dimensao correta, e chaves nunca saem do servidor.
Sugestoes de proximo passo
Implemente autentificacao e controle de custos, adicione testes de integracao que validem formato do vetor e sucesso de FT.CREATE/FT.SEARCH, e monitore latencia e uso de memoria do Redis em producao. Pequenas otimizações no formato de armazenamento e tuning do indice fazem grande diferença em escala.
Resumo final: evite a exposicao de chave, armazene vetores em formato binario float32, crie indices adequados e trate consultas KNN no servidor. Esses passos cobrem as armadilhas mais comuns e te deixam pronto para aplicar busca semantica em apps reais.
Checklist tecnico rapido
- Gerar embeddings no servidor
- Converter array de floats para Float32Array e Buffer
- Usar FT.CREATE com DIM correto e TYPE FLOAT32
- Consultar com KNN e parametrizar k
- Adicionar limitacao e caching
Fim. Use estes exemplos como base, ajustando nome do modelo, dimensao e parametros Redis conforme atualizacoes do provedor de embeddings e do Redis. Boa pratica em 2026: tratar embeddings como first class citizens na arquitetura de busca.
Assuntos: Next.js, IA, NoSQL, embeddings, semantic-search
Leia também
Caching avançado no App Router: Cache-Control, tags e Edge para rotas dinâmicas
Guia prático para caching avançado no App Router do Next.js: combine Cache-Control, revalidate, next/cache tags e edge routes para controlar performance e…
Formulários sem JavaScript com Server Actions no Next.js (App Router)
Aprenda a criar formulários totalmente funcionais sem JavaScript usando Server Actions no Next.js (App Router). Exemplo prático com código, validação…
ISR on-demand escalável no Next.js App Router com webhooks, middleware e filas
Arquitetura prática para ISR on-demand no Next.js App Router: webhooks que enfileiram eventos em Redis, worker com retry/dedupe e endpoint interno que executa…
Uploads grandes no Next.js App Router: Server Actions + upload resumível para S3
Fluxo prático para uploads grandes com Next.js App Router: use Server Actions para criar multipart uploads e gerar presigned URLs, e implemente no cliente…
A/B testing no Next.js (App Router): middleware, cookies e ISR passo a passo
Guia prático para implementar A/B testing no Next.js App Router: use middleware para atribuir variantes, cookies para persistência e rewriting para habilitar…
WebAuthn sem senha no Next.js (App Router) + Supabase: passo a passo
Guia prático e passo a passo para implementar WebAuthn sem senha em Next.js (App Router) com Supabase: conceitos, endpoints server, componente cliente e dicas…