bruno.exePROJECTScontact.exe
JJ MARINE

</jj marine>

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

ROLETECHNICAL LEAD
DATE2024 to present
TYPEONGOING ROLE · WEB · SEO · AI SYSTEMS
STATUSONGOING · SITE LIVE
STACKnext.js · typescript · tailwind · supabase · next-intl · playwright · json-ld · multi-agent +2 more
LINKS[live ↗]
AVAILABILITYjjmarine.ae
BEFORE: jjmarine.ae (Wix)
JJ Marine before, the legacy Wix homepage at jjmarine.ae
AFTER: the rebuilt site (live at jjmarine.ae)
JJ Marine after, the rebuilt homepage that is live at 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.

== WHAT IS THIS ==

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.

== </live site> ==

LIVE SITEjjmarine.ae[open in a new tab ↗]
The JJ Marine home page: a yacht at sea under the dealership’s navigation and search
jjmarine.ae, livestill image · the live site loads when this is on screen

== </what clients have said> ==

“An immense pleasure working with him. Unmatched professionalism.”

Khaled Al Khaled, JJ Marine client, Jeanneau Merry Fisher 1295 Fly

Relayed by Bruno Jaamaa with the client’s consent, October 2026.

jjmarine.ae: home
yachts for sale: the pre-owned listings
boat detail: gallery, price and enquiry on one page
boat detail on a phone: specs on the page, not in a PDF
find your yacht: from brief to berth in four steps
charter fleet
home on a phone
the old wix site it replaced

== </the problem> ==

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.

== </the approach> ==

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.

== </the story> ==

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.

== </architecture> ==

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.

== </key features> ==

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.

== </key decisions> ==

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.

== </what was learned> ==

>

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.

next:

Finish the nameserver move at the registrar (the A records are already swapped), then pick an email provider so a new lead notifies someone.

== </full stack> ==

next.js · typescript · tailwind · supabase · next-intl · playwright · json-ld · multi-agent · seo · vercel