All Work
RESTAURANT / NL

BBQ Halab.

A Syrian grill restaurant in Rotterdam needed a menu and reservation system fast. Built and shipped in one day: Dutch and Arabic side by side, zero backend, the owner manages the menu himself from a private admin page.

Web DesignBilingualSupabase
Role
Design + Build
Timeline
One day, derived from file mtimes and Vercel project creation (2026-04-26)
Live URL
bbq-halab.vercel.app ↗
BBQ Halab homepage

01 / THE REQUEST

A menu site,by tomorrow.

The owner needed a working site fast: a bilingual menu for a Dutch and Syrian customer base, a way to take table reservations without a phone line jammed at dinner rush, and a way to update dishes and prices himself without calling a developer every time.

02 / THE PROBLEM

Two languages,one page.

Most bilingual restaurant sites bolt on a language switcher: pick Dutch or Arabic, reload, read one at a time. That fails here, because the actual customer base reads both on the same visit, often mid-sentence, checking a dish name against its Arabic original. A switcher also means every RTL bug shows up only when someone actually flips into Arabic, by which point it has shipped.

03 / THE CHALLENGE

Bilingual atthe schema.

The fix sits in the database, not the frontend. Every menu row carries both languages as separate columns, name_nl and name_ar on both menu_categories and menu_items (migration.sql:10-27). There is no active/inactive language state to manage: both strings exist on the same row and render together, so a missing translation is a null field, not a routing bug.

RTL is applied per element with an .ar class carrying direction: rtl (src/base.css:34), not on the <html> or <body> tag. That means Arabic spans flip direction inline next to Dutch text flowing left to right in the same line, which is what the menu actually needs. Amiri serves the roughly 150 real Arabic menu strings, loaded from Google Fonts alongside Playfair Display and DM Sans for the Latin text (src/base.css:2).

Reservation status is enforced at the database, not in application code: status text default 'pending' check (status in ('pending','confirmed','declined')) (migration.sql:39). Any code path that tries to write an invalid status value fails at the Postgres layer, before it reaches a UI that assumes one of three states.

There is no backend server. The reservation form calls supabase.from('reservations').insert(payload) directly from the browser (src/reserveren.js:42), and the owner's admin page uploads menu photos straight to Supabase Storage from a file input (src/admin.js:291-308). No API route sits between the browser and the database.

04 / THE SOLUTION

What gotbuilt.

Bilingual menu, schema-first

Every dish carries name and description in both Dutch and Arabic on the same row. No switcher, no reload, no missing-translation routing bug.

Direct-to-database reservations

The booking form inserts straight into Supabase from the browser. Status is constrained at the database, not trusted to application code.

Owner-editable menu

A private admin page lets the owner add dishes, change prices, and upload photos directly to Supabase Storage, no developer needed for a menu change.

Zero-framework MPA

Four static entry points built with Vite 5, no React or Vue runtime shipped to the browser.

05 / THE RESULT

Live, inone day.

BUILT WITH

Vite 5SupabaseVanilla JSPostgres RLS

Need something this fast?

Next project

Yusuf Kebab