Divino
The map of Spanish wine
Divino is an interactive map to discover Spanish wineries. The idea came from a frustration, in which in the wine world you always see the same four names and the same denominations, while hundreds of small wineries that deserve attention remain invisible. In Divino every winery takes up the same space on the map, regardless of its size, and whoever discovers one lands directly on its own page. It is, above all, a project about giving visibility to those who normally do not have it. What started as a map with a few pins ended up being an engineering exercise from start to finish, and what follows is how it is built and why I made each decision.
Architecture: Next.js 16 rendered at the edge
The application is Next.js 16 with React 19 (Server Components and server-side
rendering), but it does not run on a traditional always-on Node server. It is compiled with the
OpenNext adapter and deployed on Cloudflare Workers, running at the edge,
close to the user. I chose this model over a classic Node backend for three reasons: it scales to zero (I
do not pay for an idle machine), latency drops by being served from the nearest node, and it forces
more disciplined code. In the edge runtime you do not have the
Node standard library, only standard web APIs. That is why, for example, the cryptography in the project uses
Web Crypto (crypto.subtle) instead of node:crypto. Working with
that constraint is the closest I have come to touching the real trade-offs of serverless computing
instead of just reading about them.
Data: SQLite at the edge with Cloudflare D1
The database of wineries and denominations is Cloudflare D1, which is managed SQLite
co-located with the Workers. A traditional managed Postgres was intentionally discarded, since for a read-dominated
workload and a modest schema, an always-on Postgres is overengineering the problem and adds another piece
of infrastructure to maintain. D1 lives on the same platform as everything else, there is no
server to administer and queries use prepared and parameterised statements
(prepare().bind()), which also closes the door to SQL injection by design. Real-world data is a mess, for example,
the same region written five different ways,
inconsistent labels, duplicates..., so I built a normalisation and integrity process
(cleaning, deduplication and reconciliation against a single source of truth) before trusting anything on the
map. Treating data quality is key to management.
The map: vector tiles over WebGL
The map combines Leaflet (for the interaction layer, markers and controls) with MapLibre GL as a vector tile renderer over WebGL, without rasterising any image. With vectors the map stays sharp at any zoom or pixel density, restyling happens on the client from a single style file, and zoom and panning are smooth because drawing is handled by the GPU. The tiles are served by OpenFreeMap (OpenStreetMap data). I ruled out paid map providers like Mapbox or Stadia to avoid depending on an API key with limits and usage-based billing. Instead I start from OpenFreeMap's Positron style and restyle it entirely in code to nail the Alidade Smooth look I wanted, palette, administrative boundaries, labels in Spanish, self-hosted typography...
Real denomination boundaries
On top of the base map I draw the real boundaries of each denomination of origin as geospatial polygons (GeoJSON), not as approximate circles. The level of detail adapts to zoom so that coasts and borders stay sharp up close and fade gracefully when zooming out. The key here is finding the balance between quality and resource cost. The polygons had to be simplified without destroying their shape and the payloads had to stay small to avoid penalising startup. I had never touched geospatial data before (projections, geometry simplification, the balance between fidelity and weight), solving them as concrete problems.
Transactional email and deliverability
Divino also communicates with wineries, which pulled me down the rabbit hole of getting email to actually arrive. Sending a message programmatically is easy, getting it past spam filters and into the inbox is a small systems problem in its own right. Sending is done with Resend, configuring the full authentication stack on a custom domain, SPF, DKIM and DMARC, and dealing practically with how DNS propagation and negative caching work (the same record can be visible on one resolver and invisible on another for a while). On the app side, delivery status arrives via signed webhooks. Before trusting an event I verify its HMAC-SHA256 signature with Web Crypto, reject timestamps outside tolerance to prevent replays and compare the signature in constant time.
Front-end: built for everyone, mobile-first
The interface is built with React 19 and Tailwind CSS 4, with a responsive and mobile-first approach. The filtering, map interactions and design had to feel native on mobile, not forced adaptations, so the design had to be planned almost from scratch too.
What I take away
Divino is the project where all the theory from the degree comes together. Edge computing, serverless databases, geospatial rendering over WebGL, data modelling and cleaning, DNS and email protocols, applied cryptography, front-end performance… I take away a way of thinking about building software from start to finish.
Now, to keep maintaining and expanding the website's features. I will add an embed of the site here later, when I expand the development for third-party use. In the meantime, it can be seen at mapa.wine.