</jj marine>
Yacht dealership in Dubai: its website, SEO and internal AI systems.
live
jjmarine.ae · redesigned several times since 2024
48
boats · every one its own page and structured data
4
languages · English, Arabic (RTL), Russian, French
384
sitemap URLs
SPECIFICATIONS
| ROLE | TECHNICAL LEAD |
|---|---|
| DATE | 2024 to present |
| TYPE | ONGOING ROLE · WEB · SEO · AI SYSTEMS |
| STATUS | ONGOING · SITE LIVE |
| STACK | next.js · typescript · tailwind · supabase · next-intl · playwright · json-ld · multi-agent +2 more |
| LINKS | [live ↗] |
| AVAILABILITY | jjmarine.ae |


JJ Marine sells and charters yachts in Dubai, and before the latest rebuild it did so off a Wix version of its site.
JJ Marine is a yacht and boat dealership in Dubai, where Bruno Jaamaa has been Technical Lead since 2024. The first phase of the role was the website and its search visibility: a long-running company website that he has redesigned several times since 2024, now with a database-driven catalogue of 48 boats in English, Arabic, Russian and French, live at jjmarine.ae. The current phase is AI systems for the business itself.

“An immense pleasure working with him. Unmatched professionalism.”
Relayed by Bruno Jaamaa with the client’s consent, October 2026.
JJ Marine sells and charters yachts in Dubai, and before the latest rebuild it did so off a Wix version of its site. The catalogue lived as PDF brochures, which meant the specifications buyers search for existed nowhere a crawler or an answer engine could read them, and the inventory could only be updated by hand, page by page. A dealership whose stock turns over cannot run on hand-edited pages. It also sells into a market where the buyer is as likely to read Arabic or Russian as English, and the old site was English only.
Rebuild the site as a database-backed catalogue where the boat row is the source of truth, then treat search visibility as an output of the data model rather than a marketing layer added afterwards. Every boat renders its specifications as on-page HTML and emits Product plus Offer structured data from the same row. The migration off Wix was written down before launch as 15 redirect rules and 4 hard 410s, plus 7 redirects for the old brochure PDF deep-links. The build itself ran as agent work under a coordination protocol, with adversarial review agents as a scheduled phase.
role & context
Technical Lead at JJ Marine, 2024 to present. It is a continuing role rather than a fixed engagement. By his own account: "I do everything from the website to client relations, fueling, taking clients out on the boat, managing SEO and outbound, newsletters, and our Instagram account." The early phase was the website and SEO. The current phase is AI systems for the business. By his account that is a Telegram bot with retrieval over the business’s systems that drafts client replies, financials and boat decks; nothing goes out until he or the boss approves it. No lead or traffic figures appear on this page.
JJ Marine is a yacht and boat dealership in Dubai that sells new and pre-owned boats and runs a charter fleet. Bruno Jaamaa has worked there since 2024 and holds the title of Technical Lead. The role has had two phases: first the website and its search visibility, and now AI systems for the business itself.
The company website has been live for years, and Jaamaa has redesigned it several times since 2024. Before the latest rebuild it ran on Wix, in English only, and its catalogue was delivered as PDF brochures. The specifications a buyer searches for were not readable by a search engine or an answer engine, and stock had to be updated by hand, page by page.
The first phase was the website and SEO. The current version at jjmarine.ae replaced the Wix one. It holds 48 boats (27 power, 12 sail, 5 pre-owned, 4 charter) in a Supabase table, where every row generates its own page, its specifications as on-page text and its structured data. The site is published in four languages, English, Arabic (laid out right to left), Russian and French, and its sitemap lists 384 URLs. Sixteen editorial guides sit beside the catalogue. The move off Wix was written down before launch as 15 redirect rules and 4 pages marked as permanently gone.
The current phase is AI systems for the business. By his account he built a Telegram bot with retrieval over the business's systems that drafts client replies, financials and boat decks; nothing goes out until he or the boss approves it. The stated aim is to leave more of the day for client relationships and face-to-face work. No further detail of the internal systems is published here. The public site is a separate matter: it has no chatbot and nothing customer-facing that talks to a model.
The role is carried out in person as well as in code. By his own account, Jaamaa does "everything from the website to client relations, fueling, taking clients out on the boat, managing SEO and outbound, newsletters, and our Instagram account." He has said that he walked a client through the purchase of a Jeanneau Merry Fisher 1295 Fly.
The rebuild itself was planned as 7 epics and about 50 short sprints. By launch it had run to 111 sprint commits, numbered up to S165, across 22 epics, and it has kept going since. Epics 7 through 21 were all emergent, driven by feedback while the site was already running: a matcher for buyers who do not know what they want, mobile touch behaviour, four languages, a launch runbook, a self-hosted analytics layer, an admin worklist. The gap between the plan and the log is the shape of building in-house, where the people who own the business see the site every day and keep having opinions.
The site was built by AI coding agents working under a written protocol. Sprint S156 wired the repository into an Aethereum coordination room with 13 agent surfaces, and the repository's AGENTS.md makes the loop mandatory: pull team context before touching cross-service code, share intent, declare a contract for every new interface, claim and release before a refactor, check the blast radius before changing a contract others depend on. Briefs from one agent to another are committed artifacts. Adversarial review agents are a phase in the build: S140 fixed three live search defects found in S138's own code, and S162's three headline bugs came out of adversarial review rather than from a user.
It also collided. The project's plan records that sprint numbering is broken for 5 August 2026, because two agents worked the repository concurrently and S156, S157, S158, S159 and S160 each name two different commits. S162's commit body records two renames being swept into the other agent's commit, because git add -A from either working tree picks up whatever the other has staged. Nobody lost work, but the history is no longer readable by sprint number and has to be disambiguated by hash. That cost is recorded in the repository rather than tidied away.
Next.js 16.2.9 App Router on Turbopack, React 19.2.4, TypeScript strict, Tailwind CSS v4 with @theme tokens, Motion 12.42 under LazyMotion, next-intl 4.13, Supabase, Vercel. 28 public page routes. The accent colour is a single token, so a rebrand is a one-line change rather than a search-and-replace across components.
The catalogue is the spine. Boats live in a Supabase table and every row generates its detail page and its Product plus Offer JSON-LD from the same source, alongside filter, sort, facet and full-text search over the collection. Specifications render as on-page HTML instead of living only inside a brochure PDF, which was the single biggest search and answer-engine gain in the build, and the brochures stay available as downloads. dynamicParams is false on /boats/[slug], so a slug that is not in the table returns a true 404 rather than a rendered empty shell; boats that do exist revalidate hourly through ISR.
Internationalisation runs across en, ar-AE, ru and fr with localePrefix 'as-needed' and full RTL for Arabic. Sprints S143 to S147 alone moved roughly 500 strings, on top of 4 metadata catalogs at 146 keys each, 144 boat translations and 48 translated guides. That is what turned an English-only site into a 384-URL sitemap with an hreflang matrix and x-default across all four languages.
The discovery layer is deliberately wide: JSON-LD for AutoDealer, CollectionPage, Product with Offer, BreadcrumbList, WebSite, BlogPosting, Person, Service and FAQPage, an Atom feed, llms.txt and llms-full.txt, an AI-crawler group in robots.txt naming 12 bots (GPTBot, ClaudeBot, meta-externalagent and others), plus an IndexNow key file and ping script. Lead capture writes to an insert-only Supabase table behind RLS, with Turnstile and a honeypot live and Upstash rate limiting wired but fail-open until its keys are set in production. Analytics is self-hosted: a site_events table and a navigator.sendBeacon client, with no third-party analytics as a dependency (Vercel's own Analytics and Speed Insights tags stay mounted as a free cross-check, but nothing reads from them). A password-gated /admin, unlinked, disallowed in robots.txt and excluded from the sitemap, carries a leads worklist, traffic, boats ranked by enquiries and CSV export. A separate social pipeline renders posts and slides from live catalogue data through satori and resvg and pushes them to Buffer as drafts.
Database-driven catalogue
48 boats (27 power, 12 sail, 5 pre-owned, 4 charter) in a Supabase table. Every row generates its own page, its on-page HTML specs and its Product plus Offer JSON-LD. Filter, sort, facets and full-text search across the collection.
Four locales including RTL Arabic
en, ar-AE, ru and fr through next-intl with localePrefix 'as-needed', full right-to-left layout for Arabic, an hreflang matrix with x-default, and 384 sitemap URLs.
Migration off Wix
15 redirect rules plus 4 hard 410s for the old Wix URLs, plus 7 redirects for old brochure deep-links. Pages that are gone for good say so.
AI-discovery surface
llms.txt and llms-full.txt, an AI-crawler group in robots.txt naming 12 bots (GPTBot, ClaudeBot, meta-externalagent and others), nine JSON-LD types, an Atom feed, and an IndexNow key file with a ping script.
Self-hosted analytics and a gated admin
A site_events table and a sendBeacon client: first-party data the dashboard reads, with no vendor API as a dependency. A password-gated /admin, unlinked and excluded from robots and sitemap, holds the leads worklist, boats ranked by enquiries, traffic and CSV export.
Agent coordination protocol
The repo is wired into an Aethereum room with 13 agent surfaces. AGENTS.md makes the loop mandatory: team context before cross-service code, declared contracts for every new interface, claims before refactors, blast-radius checks before contract changes.
Adversarial review as a build phase
Review agents run against the build's own output. S140 fixed three live search defects found in S138's code, S143 to S147 closed a copy gap a two-agent translation review surfaced, and S162's three headline bugs were review findings, not user reports.
DECISION 01
Specifications ship as on-page HTML, not as a PDF download, because a brochure is invisible to both crawlers and answer engines. The cost is real: someone has to keep 48 rows of structured spec data accurate, where before the brochure was the single artifact and it updated itself when the manufacturer sent a new one. The PDFs were kept for buyers who want them and the maintenance burden on the table was accepted, because a dealership that cannot be found in a spec search is not being found at all.
DECISION 02
dynamicParams is false on the boat route, which means an unknown slug returns a 404 instead of rendering. The tradeoff is that a newly added boat is not reachable until the next build or revalidation, rather than appearing the instant its row lands. A yacht catalogue accumulates dead slugs as stock sells, and a soft 200 on a boat that no longer exists is worse for both the buyer and the index than an honest 404. Existing boats revalidate hourly, which bounds the staleness.
DECISION 03
The build runs under a mandatory coordination protocol, and the case study publishes what that cost. Two agents worked the repository concurrently on 5 August 2026 and the sprint numbering broke: S156 through S160 each name two different commits, and S162's body records two renames swept into the other agent's commit, because git add -A from either tree picks up the other's staged work. The protocol prevented interface collisions, which is what it is for. It did not prevent working-tree collisions, which nothing in the loop was watching. The conclusion drawn is that agent coordination has to reach down to the index and the branch, not stop at the contract layer.
DECISION 04
Analytics is self-hosted rather than depending on a third-party product. A site_events table plus a sendBeacon client means the data is first-party and fully owned by the business in a jurisdiction where that matters, and nothing on the dashboard depends on a vendor's API. Vercel's own tags stay mounted as a free cross-check, but they are never a data source. The cost is that everything a hosted product gives away has to be built: retention, purge tooling, an environment guard, the dashboard itself. S160 caught the guard failing on localhost, where an undefined VERCEL_ENV short-circuited the check and let local browsing write into the live table. Eleven rows of local development traffic were sitting in production data.
DECISION 05
The leads worklist exists because lead notification does not yet work, and the banner on it says so. WhatsApp alerting was removed, the email sender no-ops, and the API key is unset in production, so a lead inserts and nobody is told. Rather than pretend, /admin became the inbox: a worklist pinned to the top of the page, a handled_at timestamp that is deliberately separate from the status vocabulary so that marking something handled cannot claim the customer was contacted, and a getWorklist() that takes no arguments at all, so no future date filter can quietly let an old unhandled lead age out of the queue. The banner deletes itself when email is wired. That is the current state, not a finished one.
Agent coordination that stops at the contract layer is not enough. Two agents declared their interfaces correctly and still collided in the working tree, because git add -A from either tree stages the other's work. The protocol has to cover the index and the branch, not just the shape of the API.
A plan of 50 sprints became 111 by launch because the business could see the site the whole time. Continuous visibility is the right call and it is also what makes an estimate meaningless. The first 7 epics could be quoted; everything after them was shaped by feedback.
The environment guard is the thing to test, not the feature. An undefined VERCEL_ENV short-circuited a boolean and let local development write straight into the production analytics table, and it was only caught by reading a dashboard number that could not possibly be real.
Saying plainly what the public site does not do is more useful than a vague AI claim. The public site has no chatbot and nothing customer-facing that talks to a model. The internal bot only drafts, and by his account nothing goes out until he or the boss approves it.
Finish the nameserver move at the registrar (the A records are already swapped), then pick an email provider so a new lead notifies someone.
next.js · typescript · tailwind · supabase · next-intl · playwright · json-ld · multi-agent · seo · vercel
ONGOING · SITE LIVE · jjmarine.ae