A Casa di Dante — full-stack e-commerce in five languages
Astro and .NET 10, five languages, payments, GDPR and technical SEO built by hand.
- Astro 7
- React
- Tailwind CSS
- .NET 10
- EF Core
- PostgreSQL
- PayPal
- SignalR
- Docker
The problem
A Tuscan artisan food producer wanted to sell directly across Europe. Off-the-shelf platforms charged a percentage of every order and handled neither mandatory food information nor five language markets properly.
Outcome
A shop in five languages, with server-side shipping calculation, PayPal payments confirmed by signed webhook, price history compliant with the EU Omnibus directive, and a Google Shopping feed.
Why not an off-the-shelf platform
Turnkey solutions hold up while the requirements are standard. These were not: mandatory food information, five languages with separately indexable URLs, the price history the Omnibus directive requires, and a per-order percentage that hurts on food margins. Building it cost time once.
The parts that turned out to be hard
Shipping has exactly one source of truth. Costs and the free-shipping threshold live in the backend and are served from a dedicated endpoint. Everything that prices anything asks that endpoint. The temptation to hardcode “€49” in the frontend is strong, and it eventually produces a basket that promises one figure and an order that charges another.
Prices are computed on the server, always. The order total — shipping included — is determined and stored server-side at creation time. What arrives from the browser is a request, not an amount.
Price history is not a legal footnote. The Omnibus directive requires showing the lowest price charged in the previous thirty days before a discount. That needs a real historical table: it is a domain requirement, not a disclaimer.
The wrong database comparison produces zero valid results. Product search uses a case-insensitive
comparison, tokenised word by word. With a naive Contains, PostgreSQL made “chianti” return zero
results and “Chianti” return nine — a bug that makes no noise and quietly corrupts the search
statistics too.
Compliance, properly
Six legal documents translated into five languages, consents recorded and revocable, a GDPR area where users export or delete their own data, and mandatory food information on every product. Search analytics are anonymous by design: only the normalised term, the result count and the language. That boundary is what keeps the whole feature outside the scope of personal data.
Technical SEO
A server-generated sitemap rather than the standard plugin, which only saw prerendered pages — it
published the admin area and omitted every product. Structured data, hreflang across five
languages, self-hosted fonts, server-side image resizing with caching, and no cached rendered product
lists: price, stock and the Omnibus reference price all change underneath a cache, and serving them
stale means showing a wrong price.
Realtime and payments
Customer-to-seller chat over SignalR. PayPal payments through the Orders API v2: the order is created and captured server-side, and the signed webhook is the source of truth for “paid” — not the browser redirect, which a user can close halfway.