Cómo construí Maestro Cocinero: un portal de recetas con Next.js y PostgreSQL
Maestro Cocinero es un portal de recetas personalizadas que nació de un problema doméstico: tienes ingredientes en la nevera pero no sabes qué cocinar con ellos. La mayoría de aplicaciones de recetas te obligan a buscar por nombre del plato. Este proyecto invierte el proceso.
En este artículo explico las decisiones de diseño y arquitectura que tomé durante el desarrollo: por qué elegí Next.js 14, cómo modela la búsqueda por ingredientes en PostgreSQL y cómo simplifiqué el despliegue con Docker Compose.
El problema a resolver
El eterno dilema de "¿qué cocino hoy?" no es un problema de inspiración, sino de información. Tienes ingredientes disponibles, pero los portales de recetas existentes no están diseñados para que empieces por ahí. Quería construir algo que funcionase como un motor de búsqueda inverso: parte de lo que tienes, no de lo que quieres hacer.
Los requisitos del proyecto eran claros desde el principio:
- Buscador por ingredientes con ranking de coincidencia
- Filtros dietéticos: vegetariano, vegano, sin gluten, sin lactosa
- SSR para que cada receta sea indexable por buscadores
- Diseño mobile-first: la mayoría de usuarios consultan desde el móvil
- Despliegue reproducible en un VPS propio sin dependencia de plataformas externas
Por qué Next.js 14 con App Router
La decisión más importante al principio fue elegir entre una SPA pura y un framework con SSR. Para un portal de recetas, el SEO es crítico: cada receta es una URL indexable con su propio título, descripción e imagen. Eso descartó las soluciones puramente client-side.
Elegí Next.js 14 con el App Router por varias razones:
- Server Components permiten hacer las queries a PostgreSQL directamente en el servidor sin exponer lógica al cliente
- El sistema de rutas basado en el sistema de archivos encaja bien con la estructura de un portal de contenido (categorías, recetas individuales, buscador)
- El soporte nativo para metadatos dinámicos con la API
generateMetadatasimplifica el SEO por página sin configuración extra - TypeScript de serie, sin configuración adicional
El buscador por ingredientes en PostgreSQL
El núcleo del proyecto es el buscador. Una receta tiene múltiples ingredientes, y el usuario selecciona los que tiene disponibles. El portal debe devolver las recetas ordenadas por el porcentaje de ingredientes que el usuario ya posee.
Modelé la base de datos con tres tablas principales: recipes, ingredients
y una tabla de unión recipe_ingredients. La query de búsqueda calcula el porcentaje de
coincidencia como:
Esta aproximación es eficiente para el tamaño del catálogo actual y permite añadir fácilmente filtros adicionales (tiempo de preparación, dificultad, tipo de dieta) como condiciones WHERE antes del GROUP BY.
Decisión de diseño: Usé Prisma como ORM para las operaciones
CRUD estándar, pero las queries de búsqueda con ranking las ejecuto con prisma.$queryRaw
para mantener el control total sobre el SQL sin perder la seguridad de los parámetros tipados.
Infraestructura con Docker Compose
Desde el primer día quise que el despliegue fuese reproducible: el mismo docker-compose.yml
que uso en local debe funcionar en producción sin modificaciones. El stack tiene tres servicios:
- app: la imagen de Next.js construida con un Dockerfile multistage que separa la fase de build de la imagen final
- db: PostgreSQL con un volumen persistente para los datos
- proxy: Nginx como reverse proxy con terminación TLS mediante Certbot
El mayor reto fue gestionar las migraciones de base de datos sin downtime. La solución fue añadir
un paso de migración en el entrypoint de la imagen de la app que ejecuta
prisma migrate deploy antes de arrancar el servidor de Next.js. Así las migraciones
siempre se aplican antes de que la aplicación acepte tráfico.
Diseño mobile-first con Tailwind CSS
El diseño parte de la pantalla más pequeña. La mayoría de usuarios consultarán el portal desde el móvil, delante de la nevera o en el supermercado, así que la interfaz tiene que ser rápida de usar con una sola mano. Los breakpoints de Tailwind fueron suficientes para implementar la progresión de diseño sin necesidad de media queries personalizadas.
Para el rendimiento, aproveché la optimización de imágenes de Next.js con el componente
Image y las fotos de recetas se sirven en formato WebP con lazy loading automático.
El resultado es un Largest Contentful Paint por debajo de 2 segundos en conexiones 4G.
Lecciones aprendidas
- El App Router de Next.js 14 tiene una curva de aprendizaje real: los límites entre Server Components y Client Components no siempre son obvios al principio
- Docker Compose es suficiente para proyectos de este tamaño; Kubernetes sería sobredimensionado y añadiría complejidad sin beneficio claro
- Modelar la búsqueda en SQL desde el principio fue la decisión correcta: las alternativas con Elasticsearch o búsqueda en memoria habrían complicado el stack sin mejora apreciable para el volumen de datos actual
- El SSR de Next.js acelera el desarrollo de funcionalidades SEO, pero hay que planificar bien qué partes necesitan hidratación en cliente para no sobrecargar el bundle
Ver Maestro Cocinero en producción
Entra al portal, busca recetas por ingredientes y descubre qué puedes cocinar hoy.