7.2 KiB
O cache JSON do servidor vem no repositório (data/products-cache.json) com produtos reais do Open Food Facts. Contado por Nutri-Score, num clone novo e sem instalar nada:
$ node -e "const p = Object.values(require('./data/products-cache.json').products); const g = {}; for (const x of p) g[x.nutrition_grades] = (g[x.nutrition_grades] || 0) + 1; console.log(p.length, g)"
232 { e: 26, unknown: 167, d: 24, c: 9, a: 2, b: 4 }
O que é
NutriScan é um app web em Next.js para buscar produtos alimentícios no Open Food Facts e montar listas de compras. A entrada é detectada sozinha: 8 a 13 dígitos viram busca por código de barras; o resto, busca por nome ou marca.
Páginas: / (landing), /resultados, /produto/[barcode], /lista/[id] e /sobre.
| Recurso | O que faz |
|---|---|
| Filtros | Nutri-Score A–E, grupos NOVA 1–4, categorias, marcas, alérgenos e aditivos (incluir/excluir, ou "sem aditivos"), faixas por 100 g de energia, gordura, carboidratos, proteínas, açúcares, fibras e sódio, e países |
| 12 critérios de ordenação | Relevância, Nutri-Score, nome, marca, energia, gordura, açúcares, completude, com imagem, com dados nutricionais, Eco-Score e grupo NOVA, em ordem crescente ou decrescente |
| País por IP | A rota /api/location descobre o país do visitante e a busca prioriza produtos dele; o resultado fica 7 dias no localStorage, com Brasil como fallback |
| Listas de compras | Várias listas acessíveis por ID em /lista/[id]; cada item tem nome, quantidade, unidade e status de comprado, persistidos com Prisma + SQLite |
| Interface | Tema claro/escuro, componentes shadcn/ui sobre Radix UI, animações com Framer Motion, skeletons, paginação e manifest de PWA |
Por que usar
- Menos chamadas ao Open Food Facts. A busca passa por dois caches antes da API:
localStorageno navegador (TTL padrão de 1 hora) e o cache JSON no servidor. Tudo que vem da API é gravado no cache JSON. - Funciona sem a API. No modo cache-only o servidor responde só com o cache JSON, sem consultar o Open Food Facts.
- Filtros combinados: Nutri-Score, NOVA, alérgenos, aditivos e faixas de nutrientes ao mesmo tempo, com 12 ordenações.
Quando não usar: se você precisa publicar o app ou usá-lo comercialmente (a licença só permite uso pessoal em localhost), ler código de barras pela câmera (a entrada é digitada ou colada) ou esperar Nutri-Score em cada produto (no cache acima, 167 de 232 produtos vieram sem nota).
Como funciona
- O navegador procura a busca no
localStorage(lib/cacheManager.ts,hooks/useSearchCache.ts). - Se não achar, chama as rotas Next.js
/api/searche/api/products/[barcode]. - As rotas consultam o cache JSON do servidor (
lib/jsonCacheManager.ts,data/products-cache.json). - Fora do modo cache-only, o que falta vem do Open Food Facts (
lib/openFoodFactsApi.ts: timeout de 8 s, 200 ms entre páginas, até 1000 produtos por página) e é gravado no cache JSON.POST /api/cache/synctambém grava produtos enviados pelo cliente. - As listas de compras usam Server Actions (
app/actions.ts) sobre Prisma + SQLite: modelosListaeProdutosemprisma/schema.prisma.
Início rápido
Requisitos: Node.js 18+ (o deploy usa nodejs_18) e pnpm (o deploy fixa [email protected]).
git clone https://git.evandro.dev.br/evandro/nutriscan.git
cd nutriscan
pnpm install # o postinstall já roda `prisma generate`
# .env na raiz (o Prisma lê .env, não .env.local)
echo 'DATABASE_URL="file:./db/database.sqlite"' > .env
pnpm db:push # cria o banco SQLite a partir do schema
pnpm dev # http://localhost:3000
Warning
Não existe
.env.exampleepnpm db:migratenão funciona num clone novo: ele rodaprisma migrate deploy, mas/prisma/migrationsestá no.gitignore. Crie o banco compnpm db:push. ODATABASE_URLé resolvido a partir da pastaprisma/, entãofile:./db/database.sqliteviraprisma/db/database.sqlite(pasta também ignorada pelo git).
Outros scripts: pnpm build (prisma generate && next build), pnpm start, pnpm lint (next lint), pnpm db:generate.
Configuração
| Variável | Efeito |
|---|---|
DATABASE_URL |
Banco SQLite das listas de compras (obrigatória) |
NEXT_PUBLIC_CACHE_ONLY_MODE=true |
As rotas de busca e de produto respondem só com o cache JSON |
NEXT_PUBLIC_RYBBIT_HOST |
Destino do proxy /analytics/* (padrão https://analytics.evandro.dev.br/api) |
O switch de cache-only na interface só funciona em produção (NODE_ENV=production); as rotas /api/search e /api/products/[barcode] aceitam ?cache_only=true em qualquer ambiente.
O que sai do seu servidor:
- Open Food Facts: dados e imagens de produtos (
next.config.mjsliberastatic.openfoodfacts.orgeimages.openfoodfacts.org). - ip-api.com: o IP do visitante, enviado pelo servidor por HTTP, para descobrir o país.
- Rybbit Analytics (self-hosted, via proxy
/analytics/): com session replay e rastreamento de erros. - O app também exibe um card de doação via Buy Me a Coffee.
Estado do projeto
Projeto pessoal, sem suíte de testes; o único check é pnpm lint. O último trabalho de código foi o redesign completo e os ajustes no deploy com Prisma/SQLite, em 2026-07-16. O repositório traz dokploy.yaml e nixpacks.toml para o deploy do autor, sem workflow de CI. A instância pública do autor em nutriscan.evandro.dev.br não respondia com o app em 2026-10-24.
Limites conhecidos:
- Toda busca nova depende do Open Food Facts; além do cache, não há fila nem fallback.
- A localização é por IP, não pela API de geolocalização do navegador.
- O
sitemap.xmllista só a página inicial.
Licença
Licença de Uso Pessoal, em LICENSE (Copyright (c) 2024 Evandro): uso pessoal, não comercial e só em localhost. É proibido fazer deploy público, redistribuir, usar comercialmente ou criar obras derivadas para distribuição. Dúvidas sobre a licença: [email protected].
Créditos: Open Food Facts, Next.js, Prisma, shadcn/ui e Radix UI.
Feito por Evandro Fonseca Junior · git.evandro.dev.br