Tenant architecture
Multi-language support
In production
Project Snapshot.
The essential facts. Sector, timeline, surface area, and where it lives in production.
Brief, Build, and Outcome.
The Challenge
Restaurants needed a single platform to manage menus, customer-facing pages, and growth tooling without requiring technical staff. The product had to scale across multiple locations and languages from day one.
Our Approach
We architected Dineri as a multi-tenant SaaS so each restaurant gets an isolated, fully branded experience while running on shared infrastructure.
The Outcome
Dineri is live in production at dineri.app, supporting multiple restaurant tenants across languages and locations with a single owner dashboard.
What changed.
A side-by-side of what the client had before we engaged, and what we shipped.
- Most restaurants we talked to were emailing a PDF menu to a Linktree page, or worse, asking a cousin to update a printed flyer
- Multi-location owners maintained the same menu in three places, and at least one was always wrong
- Changing the price of a single dish meant a phone call, a Photoshop edit, and a re-upload. Sometimes a day's wait
- Customers landed on the menu, pinch-zoomed a PDF, gave up, and asked the waiter
- Owners had no idea which dishes people actually looked at
- Staff could not update anything without help from the friend who originally set it up
- Nothing across the menu, the social links, or the booking page looked like the same restaurant
- Each restaurant gets its own subdomain, its own colors, its own logo. Sharing one quiet platform underneath
- One dashboard, every location, every menu. Including the one in the second city the owner kept forgetting to update
- Edit a dish, hit save, and the customer scanning the QR code at table 4 sees the new price on their next refresh
- Customer menus designed for one-handed scrolling on a phone at the table, not a desktop browser
- Owners can see exactly which dishes get viewed, which get skipped, and which photos people actually open
- The onboarding flow is short enough that the owner finishes it themselves before we get on the call
- Customer-facing pages feel like the restaurant. Same fonts, same colors, same warmth. Not like Dineri
Achievements & deliverables.
We built Dineri so a restaurant owner with no tech background can open their phone at the table, change the price of a dish, and have it live before the customer puts the menu down.
Multi-tenant architecture
Each restaurant runs on isolated, branded surfaces while sharing infrastructure onboard a new tenant in minutes, not days.
Internationalization (i18n)
Customer menus and the owner dashboard fully translatable. Restaurants in any country, any language, from day one.
Per-restaurant theming
Owners can customize colors, logos, and brand voice every customer-facing page reflects the restaurant, not Dineri.
Owner dashboard
Menu builder, location management, analytics, and customer insights all in one non-technical-friendly admin.
Mobile-first menu pages
Optimized for phone scanning at the table fast loads, accessible touch targets, image-rich layouts.
Live in production
Running today at dineri.app supporting real restaurants.
How We Built It, Phase by Phase.
Every project follows a deliberate sequence. Discovery, design, foundation, build, and launch. Here is exactly what happened, and what was delivered at each step.
Sitting in Restaurants
We spent the first two weeks talking to real restaurant owners and watching how they actually scanned QR codes at their own tables. Two things became obvious fast: every owner spoke a different language, and almost none of them knew what "multi-tenant" meant. But they all instantly understood "your menu, your colors, our problem."
- Owner interviews (8 restaurants, 3 countries)
- Tenant isolation model on paper
- i18n strategy. Translatable per dish, not just per shell
- A rough roadmap the client could read without us
Designing the Menu, Not the Dashboard
We designed the customer menu first, before the admin. Most SaaS does this backwards. The whole product exists to make the menu great. So we got the menu right, then worked backward to the dashboard that produces it.
- Customer menu in 6 brand variants
- Owner dashboard built around the menu, not vice versa
- Design tokens for per-restaurant theming
- Mobile interactions tested on real phones at restaurant lighting
The Multi-tenant Bones
We made the tenancy boundary the first thing the code knows about. Every query takes a tenant ID before it takes anything else. We caught two early bugs in code review where a developer forgot to pass it. And made it a compile-time error after that.
- Subdomain routing with tenant context propagated everywhere
- Theming engine (CSS variables resolved at runtime per tenant)
- Auth + onboarding wired to the new tenant flow
- Type-level guarantee that tenant ID can't be forgotten
Menu Builder + i18n
The menu builder went through three rewrites. The first was technically clean but felt fiddly. The second was easy to use but skipped translations. The third. The one that shipped. Let owners switch languages while editing and see the customer view update in a panel beside them.
- Drag-and-drop menu builder (third attempt, charmed)
- Per-dish translation editor with live preview
- Image uploads with client-side compression
- Schema validation that prevents broken menus going live
Quiet Launch
We launched with the first three restaurants from the original interviews. One of them onboarded entirely on their own while we were on a call with another. That was the moment we knew the flow worked. Analytics and monitoring went up the same week.
- Production launch at dineri.app
- Self-serve onboarding (tested by a non-engineer in 11 minutes)
- Per-restaurant analytics on the dashboard
- Short owner-facing runbook. 4 pages, not 40
Inside Each Feature We Built.
Not just what we shipped. How it works under the hood, and the engineering decisions behind each piece.
Tenancy You Can't Forget
We made the tenancy boundary the first thing every part of the code knows about. The compiler refuses to let a developer query data without a tenant ID. Twice during the build it caught a mistake that would have shown one restaurant another's menu.
- Subdomain routing resolves the tenant before any data fetch
- Tenant ID is a non-optional argument at the type level. Forgetting it doesn't compile
- All Postgres queries pass through a function that scopes by tenant; raw queries are linted out
- Onboarding a new tenant is one workflow with sensible defaults. No engineering touch
The Menu Builder (Third Time Lucky)
The first version was technically clean and miserable to use. The second was fun but skipped translations. The third one. The one in production. Lets owners write in any language and watch the customer view update in a panel beside them.
- Live customer-view preview pane updates as the owner edits
- Language switcher inside the builder, so translating a dish is one click, not a workflow
- Optimistic updates with rollback "saving" never blocks the next edit
- Schema validation rejects broken menus at save time, not at customer-view time
Theming Without Rebuilds
Owners change their primary color in a picker, the whole product follows in real time. No build, no deploy, no waiting. We tested this with the worst color combinations we could imagine to make sure the design still held.
- Theme tokens injected as CSS variables at runtime per tenant
- Preview mode shows the change to the owner before publishing
- Contrast checked on save. Owners can pick "that color," we'll warn them if menu text becomes unreadable
- Falls back to defaults gracefully if any token is missing or invalid
Per-dish Internationalization
Most i18n libraries translate the UI shell. We needed the content too. Owners write the dish name once per language they care about. And if they skip a language, customers see the owner's primary instead of a blank line.
- Each dish, category, and description has its own translation table. Not a JSON blob
- Missing translations fall back to the owner's primary language, never to a placeholder
- Customer's language choice persists across visits via cookie
- Owner dashboard is itself available in multiple locales for non-English restaurateurs
Designed for the Table, Not the Desktop
We tested the customer menu in actual restaurant lighting on actual phones with actual hungry hands. A lot of small things changed: bigger tap targets, less reflection on photos, font sizes that survive an arm-stretched scroll.
- Touch targets sized for one-handed use over a table
- Photos served as responsive variants with lazy loading. Restaurant Wi-Fi is usually awful
- First paint optimized below 1.5s even on cellular
- Print-friendly fallback for the staff copy back-of-house
A Dashboard the Owner Actually Uses
Menu builder, locations, analytics, and customer insights in one place. Designed for people who run restaurants, not people who build software. The hardest design decisions were the ones we cut: every "power user" feature we removed made the dashboard better.
- Role-aware UI shows owners and staff different surfaces
- Live analytics on popular dishes, view counts, and traffic sources. No "export to CSV" required
- One dashboard, every location. Including the second-city venue the owner keeps forgetting about
- No jargon in any label or empty state. We rewrote them when a user asked "what does that mean?"
Every Tool, and Why We Picked It.
A grouped breakdown of every technology in the build, with the reasoning behind each choice.
Frontend
- React
We needed per-tenant theming at render time. Passing one theme object down the tree was simpler than fighting a framework about it.
- TypeScript
Made tenant ID a non-optional argument at the type level. Two bugs caught at compile time that would have caused tenants to see each other's data.
- Tailwind CSS
Theming via CSS variables wired into Tailwind tokens. Owners change their primary color in a picker, the entire UI follows, no rebuild.
Tenancy
- Subdomain routing
Each restaurant gets its own host. The router resolves the tenant before any data fetch. No "oops, wrong restaurant" possible.
- Theme tokens
Stored per tenant, injected as CSS variables. Falls back to safe defaults if an owner clears a value mid-edit.
i18n
- Per-dish translations
Most i18n libraries translate the shell, not the content. We needed the content too. A paella in Madrid and London are not always described the same way.
- Locale switcher with memory
Customer's language choice sticks across visits via a cookie. The owner can preview every translation before publishing.
Design
- Figma
We designed the customer menu before the dashboard, then matched the dashboard to it. The whole platform exists to serve that one screen.



