Skip to content
aviral gupta

Arabic and English Website UAE: hreflang and RTL Done Right

A bilingual Arabic and English website for the UAE is an engineering job, not a translation job: put Arabic on locale-prefixed subpaths, emit one reciprocal hreflang cluster per page, and treat RTL and Arabic webfonts as layout and performance work.

Written by Aviral GuptaPublished 9 min read
  • Arabic SEO
  • hreflang
  • RTL
  • Next.js
  • UAE
Arabic and English Website UAE: hreflang and RTL Done RightEVERY PAGE POINTS AT EVERY OTHER — AND AT ITSELFendex-defaultONE BROKEN RETURN LINK VOIDS THE CLUSTER

Why is an Arabic and English website a build decision, not a translation job?

Because translation is the cheapest line in the budget. Everything around it is engineering: a second URL for every page, an hreflang cluster that has to stay reciprocal through every deploy, a layout that mirrors instead of shifting, a font that shares no glyphs with your Latin one, and a keyword set that is not a translation of your English keyword set. Send a vendor a spreadsheet of strings and you get strings back. The site is still unbuilt.

The demand is not speculative. Research collated on the UAE market found that 63% of UAE internet users prefer Arabic when making a local purchase decision and that more than 60% of searches are in Arabic, while fewer than 20% of Dubai businesses maintain optimised Arabic content (Right Media, 2026). In most UAE verticals you are not competing against strong Arabic content. You are competing against none.

That is also why half-finished Arabic trees are common and worse than nothing. They fail in two shapes: Arabic URLs that 404 while your English pages still advertise them as alternates, or English text served inside <html lang="ar"> because a fallback quietly kicked in. The first voids your hreflang clusters. The second asks a search engine to choose between near-duplicate pages in the wrong language. I ran six locales here for a year, Arabic among them, and most of what follows is a defect I shipped myself first.

Should Arabic live on /ar, on a subdomain, or on a .ae domain?

On locale-prefixed subpaths: /services in English, /ar/services in Arabic. Google treats subdirectories, subdomains and country domains as equally valid ways to serve localized versions, so the choice is operational, and operationally the subpath wins for almost every UAE business.

Three URL strategies, and what each one really costs.
StructureWhat it signalsWhen it is rightReal cost
example.com/ar/…A language variant of one siteDefault choiceYour router has to be locale-aware everywhere
ar.example.comA related but separate hostArabic run by another team on another CMSSeparate crawl behaviour, separate Search Console property, duplicated infrastructure
example.ae / example.saA country, not a languageA separate country operation with its own prices, stock and legal entityAuthority restarts per domain, and you still need hreflang — now across domains

The third row is the expensive mistake. A country-code domain targets a country, not a language, and Arabic readers in Dubai, Riyadh and Doha are one audience linguistically and three markets commercially. Buying .ae to serve Arabic solves nothing and splits your equity.

The as-needed prefix needs care

Most routers let you drop the prefix for the default locale, so English is served from /services rather than /en/services. That makes /en/services a URL that should not exist, and every part of the system has to agree: canonicals, hreflang, the sitemap and the language switcher all name the unprefixed form, and the switcher writes URLs rather than leaving a cookie to decide what you meant. Get one wrong and Search Console starts reporting duplicates against your English tree.

Turn server-side language detection off while you are there. If the middleware reads Accept-Language and redirects, a crawler advertising Arabic never receives your English canonical as addressed. Let hreflang route search engines and let a dismissible banner suggest a language to humans.

import {defineRouting} from 'next-intl/routing';

export const routing = defineRouting({
  locales: ['en', 'ar', 'de', 'fr', 'es', 'hi'],
  defaultLocale: 'en',

  // English is unprefixed: '/services', not '/en/services'.
  localePrefix: 'as-needed',

  // Off: the middleware would otherwise emit its own Link: rel="alternate"
  // hreflang header built from the *request* host, which can disagree with
  // the <head> built from your canonical origin. Keep one source of truth.
  alternateLinks: false,

  // Off: with it on, an Accept-Language header 307-redirects the canonical
  // URL, so a crawler asking for Arabic never receives '/' as addressed.
  localeDetection: false
});

export type Locale = (typeof routing.locales)[number];
src/i18n/routing.ts — the live config on this site.
An hreflang cluster for a bilingual page: the English and Arabic URLs each declaring both versions plus x-default, with one missing return link breaking the cluster.EVERY PAGE POINTS AT EVERY OTHER — AND AT ITSELFendex-defaultONE BROKEN RETURN LINK VOIDS THE CLUSTER
Every page declares every version, including itself. One missing return link and the whole set can be ignored.

What does a correct Arabic and English hreflang cluster look like?

Both pages declare both versions, both declare themselves, one x-default names the fallback, and every URL returns 200 in exactly the form you wrote it. The claims are mutual: if /ar/services names /services as its English alternate and /services does not name it back, the claim is unverified and can be discarded. Multilingual audits published in 2026 put the share of multilingual sites carrying at least one broken cluster at 15-21%, which matches what I find whenever I open a live head.

export const BASE_URL = (
  process.env.NEXT_PUBLIC_SITE_URL || 'https://guptaaviral.com'
).replace(/\/+$/, '');

/**
 * Trailing slashes omitted everywhere except the bare origin. Mixing
 * '/ar/services' and '/ar/services/' across canonical, hreflang and sitemap
 * is two URLs for one page, with the signals split between them.
 */
export function localeUrl(locale: string, path: string): string {
  const prefix = locale === 'en' ? '' : `/${locale}`;
  const normalized = path === '/' ? '' : path.replace(/\/+$/, '');
  return `${BASE_URL}${prefix}${normalized}`;
}

/** `locales` narrows the cluster to the languages a route really exists in. */
export function languageAlternates(
  path: string,
  locales: readonly string[] = ['en', 'ar']
): Record<string, string> {
  const languages: Record<string, string> = {};
  for (const locale of locales) languages[locale] = localeUrl(locale, path);
  languages['x-default'] = localeUrl('en', path);
  return languages;
}

// app/[locale]/services/page.tsx
export async function generateMetadata({params}: Props): Promise<Metadata> {
  const {locale} = await params;
  return {
    // Without metadataBase your canonical resolves relative in production.
    metadataBase: new URL(BASE_URL),
    alternates: {
      // Self-referential per locale. Never canonicalise Arabic to English.
      canonical: localeUrl(locale, '/services'),
      languages: languageAlternates('/services')
    }
  };
}
One origin, one URL builder, one cluster — generated, never hand-written.

The locales parameter is the escape hatch for partial translation. A route that exists only in English should advertise only English, because an alternate pointing at a 404 invalidates the cluster it sits in.

hreflang defects, the symptom you will see, and the check that finds them.
MistakeSymptomHow to catch it
Return link missing on one sideSearch Console reports "no return tags"Fetch each alternate and diff its hreflang set against the source page
Alternate points at a redirect (apex vs www, or a trailing slash)Alternates never indexed; canonical reported as a redirectcurl -sI every alternate; anything but 200 is a defect
Alternate points at an untranslated page that 404sThe cluster is dropped for that URL, silentlyNarrow the cluster per route; assert the URL list at build time
Trailing-slash forms mixed across head, sitemap and linksTwo URLs per page, signals splitOne normalising URL builder; grep the sitemap for /</loc>
x-default missing, or on the homepage onlyNo fallback for unmatched languagesCheck the head of a deep page, not just /
HTTP Link: header disagrees with the in-page tagsTwo contradictory clusters; engines trust neithercurl -sI <url> | grep -i '^link:', then compare with the HTML
Arabic page canonicalised to the English pageArabic URLs drop out of the indexEvery canonical must be self-referential within its own locale

Why can your HTTP Link header and your in-page hreflang contradict each other?

Because two parts of the stack emit hreflang and neither knows about the other. Your <head> is built from a canonical origin compiled into the app. The Link: rel="alternate" response header, if your i18n middleware emits one, is built at request time from the host that actually served the request. They agree until your platform serves www while your code says apex.

That is exactly what I found on my own domain: in-page hreflang naming the apex, the middleware header naming www, and the apex answering a 308 on every path. Two contradictory clusters per page, one of them entirely redirects. Google worked around it. Bing dropped the URLs. The fix is to pick one emitter and delete the other — I keep the HTML, because that is the copy I can inspect with curl and diff between deploys — then pick one host, 308 the other to it, and derive every absolute URL from one environment-driven constant. It is the same discipline that keeps a rebuild from losing rankings.

What actually breaks when you set dir=rtl?

Less than people fear if the CSS uses logical properties, and almost everything if it does not. Setting lang="ar" and dir="rtl" on <html> makes the browser mirror the inline axis for you: text runs right to left, block flow stays top to bottom, and anything expressed as start or end follows. Anything expressed as left or right does not. That is the difference between a two-day RTL pass and a two-week one.

/* Wrong: pinned to a physical side, so the Arabic layout inverts badly. */
.card { padding-left: 1.5rem; border-left: 2px solid; text-align: left; }

/* Right: flips automatically under dir="rtl". */
.card {
  padding-inline-start: 1.5rem;
  border-inline-start: 2px solid;
  margin-inline-end: auto;
  text-align: start;
}

/* Mirror what points. Never mirror logos, clocks, ticks or media controls. */
[dir='rtl'] .icon-arrow { transform: scaleX(-1); }
Logical properties flip with direction. Physical ones never will.

In Tailwind those are the ps-/pe-, ms-/me-, start-/end- and text-start utilities, plus an rtl: variant for the few things that genuinely need it. Pick one mechanism for mirrored icons and use it everywhere: on my own build some arrows flipped by swapping components and others by rtl:-scale-x-100, which is the kind of inconsistency that survives review and breaks the day someone edits a shared card.

LTR islands, forms and numerals

Phone numbers, emails, URLs, IBANs and code are left-to-right runs inside right-to-left text. The Unicode bidirectional algorithm reorders the surrounding punctuation if you leave them unmarked, which is how a UAE number renders with its + stranded at the wrong end. Mark them.

<span dir="ltr" className="inline-block">+971 52 224 7789</span>

// Numerals are a decision, not a default. Check yours before you choose:
//   node -e "console.log(new Intl.NumberFormat('ar-AE').format(1234))"
new Intl.NumberFormat('ar-AE').format(1234);           // Arabic-Indic digits
new Intl.NumberFormat('ar-AE-u-nu-latn').format(1234); // 1,234
Two things every bilingual UAE build gets wrong at least once.

Most commercial UAE sites want Western digits in Arabic copy — set that explicitly with the -u-nu-latn extension rather than letting a hardcoded en-US leak through a formatter. The rest of the form layer is quick: give email, phone and password inputs dir="ltr", move validation icons to the logical side, and hand-check anything that implies forward motion, meaning progress bars, sliders, carousels and breadcrumbs.

How much do Arabic webfonts cost you in LCP and CLS?

More than a Latin-only build prepares you for, because you cannot reuse the font you have. Inter, Poppins, Space Grotesk and the rest carry no Arabic glyphs, so an Arabic locale means a second family at whatever weights your design uses — and Arabic faces are heavier per weight, since the script is cursive with initial, medial, final and isolated forms per letter.

The trap is preloading. Font loaders emit <link rel="preload"> from the module declaration, not from whether the CSS is applied, so declaring an Arabic family in a shared layout can push it at highest priority to every visitor in every language, competing with the real LCP resource. Auditing my own build against the font manifest, I found a 166 KB Arabic weight set preloaded on all six locales, including the five that never render a single Arabic glyph. Setting preload: false and applying the CSS variable only on RTL routes took preloaded font weight from roughly 207 KB to about 41 KB on five locales.

  • Subset to arabic and ship two weights, not five. Every weight is another file.
  • Keep display: swap, but expect a real swap: Arabic fallback metrics differ from the webfont, so a careless one is a CLS event, not just a flash.
  • Raise line-height for Arabic. Ascenders, descenders and diacritics need more vertical room than Latin at the same size, and a shared value tuned for English will clip.
  • Measure the Arabic route separately in PageSpeed Insights and in field data. One sitewide Core Web Vitals number hides the locale carrying an extra font.

This is where a bilingual build becomes genuine Core Web Vitals work rather than styling. Latin-only sites never hit it.

Why is translated keyword research not Arabic keyword research?

Because translation preserves meaning, and search is about phrasing. Run an English keyword list through a translator and you get grammatical Modern Standard Arabic that nobody types. Three separate causes, each needing a different response.

Register. Modern Standard Arabic is the written standard and dominates formal and informational queries; Gulf dialect appears in conversational, local and price-led ones. A UAE reader moves between them inside one session, so a page written exclusively in the formal register misses the commercial half of its own topic.

Script mixing. Arabic speakers in the UAE routinely type Latin-script brand, product and technology names inside an otherwise Arabic query, and sometimes transliterate Arabic words into Latin characters. Your Arabic page has to carry those Latin forms verbatim, not translated or transliterated ones.

Morphology. Arabic attaches the definite article and other particles directly to the word, and typed queries usually drop diacritics, so one concept surfaces as several forms with no shared exact-match string. Keyword tools handle this badly and report thin or zero volume for terms that carry real traffic.

So work empirically. Draft the Arabic page from the intent rather than the English copy, publish it, then read Search Console query data filtered to the /ar/ path prefix and rewrite from what people actually typed. Autocomplete in an Arabic-language browser profile beats most volume estimates, and three real queries from an Arabic-speaking colleague beat a whole export.

Is machine-translated Arabic content safe to publish?

Unreviewed, no. Google's spam policies name machine-translated text published without human review as an example of scaled content abuse. As a first draft that a fluent speaker edits, machine translation is ordinary practice. Piped straight to publish across ninety pages, it is the pattern the policy describes — and an Arabic-speaking buyer reading obviously generated copy learns that the Arabic side of your business is an afterthought, which is the exact impression the exercise was meant to remove.

So: parity of depth on the pages that matter, honest absence everywhere else. Twelve Arabic pages covering your services, key locations and contact routes, written properly, outperform ninety machine-translated ones and cost less. For routes you have not translated, narrow the hreflang cluster so those URLs are simply not advertised in Arabic.

What you must never do is fall back to English inside an Arabic URL. On my own build, one trailing comma in a message file made the import throw; a catch swallowed it, and that locale rendered entirely in English inside a non-English lang attribute, with a matching canonical and hreflang entry. Nothing in the build or the logs surfaced it. Fail the build on a malformed message file instead of falling back, and assert that the rendered lang matches the content.

Consider making Arabic the original rather than the alternate for parts of the GCC. UAE government and semi-government procurement is Arabic-first as a matter of course, and Saudi commercial search is overwhelmingly Arabic (market research pass, 2026). If Riyadh or Jeddah is a real market for you, write the Arabic page first and treat English as the translation.

How do you verify a bilingual build before you launch it?

With commands, not with a walkthrough in a browser. Everything above fails invisibly to a human reader: the page looks right, the cluster is broken, and the first symptom is a Search Console report six weeks later.

SITE=https://guptaaviral.com

# 1. lang and dir are correct on the Arabic tree.
curl -s $SITE/ar/services | grep -o '<html[^>]*>'

# 2. The two hreflang sets must be identical. Any asymmetry is a broken cluster.
for u in "$SITE/services" "$SITE/ar/services"; do
  echo "== $u"; curl -s "$u" | grep -o 'hreflang="[^"]*" href="[^"]*"' | sort
done

# 3. Every alternate returns 200 — no redirects, no 404s.
curl -s $SITE/ar/services | grep -o 'href="https://[^"]*"' | cut -d'"' -f2 | sort -u |
  while read -r u; do echo "$(curl -sI -o /dev/null -w '%{http_code}' "$u") $u"; done | grep -v '^200'

# 4. No response-header hreflang contradicting the HTML.
curl -sI $SITE/ar/services | grep -i '^link:'

# 5. The Arabic canonical is self-referential, not pointing at English.
curl -s $SITE/ar/services | grep -o '<link rel="canonical"[^>]*>'
The five checks I run before calling a bilingual build done.

Then split your measurement. In Search Console, keep one filter for pages containing /ar/ and one for the rest, and read impressions, queries and Core Web Vitals separately; an average hides an Arabic tree that is not being served, or an Arabic LCP dragged down by a font. In GA4, segment by language and landing page and confirm consent and conversion events fire identically on both trees — a mirrored layout is exactly where a hardcoded selector breaks.

None of this is exotic. It is a routing decision, a CSS discipline, one URL builder, a font config and a content plan you can staff — the same decisions I work through on any Next.js project for a Dubai client. If you want to know which of the seven defects in the table your live site has, send me the domain: I run these checks as an introductory project call and send you the failing commands rather than a proposal deck.

What should you know about how I work on this?

Yes — English and Arabic on one Next.js codebase, with Arabic served from locale-prefixed URLs, a reciprocal hreflang cluster on every route, RTL handled with logical CSS properties rather than a mirrored stylesheet, and Arabic typography configured so it does not damage your Core Web Vitals. I build and ship the platform and the technical SEO around it. Translation and Arabic copywriting I coordinate with a native speaker rather than pretending to do myself, because the difference between fluent Arabic and merely correct Arabic is obvious to your buyers and invisible to me.

I implement RTL properly, which is mostly an engineering question: `dir="rtl"` on the document, logical properties so spacing and borders flip on their own, directional icons mirrored by one consistent mechanism, LTR islands for phone numbers, emails and code, and explicit decisions about numerals and line height. Where a layout genuinely needs a different composition in Arabic rather than a mirrored one, I will say so and we scope it. What I will not ship is a second stylesheet with every left and right swapped by hand, because it drifts from the original within a sprint.

On the build and measurement side, yes: both language trees indexable and correctly clustered, GA4 and Google Tag Manager configured so Arabic and English traffic read separately, conversion events firing identically on a mirrored layout, and ad pixels verified on both before any budget runs. Campaign management and Arabic ad copy are not services I sell, and I will work alongside whoever handles them. What I add is making the Arabic side measurable, so you can tell whether it is earning its place instead of guessing.

I help structure it and I will draft English technical pages, but Arabic copy should be written or thoroughly edited by a fluent speaker. What I can remove is the guesswork underneath: which routes need Arabic first, keyword targets derived from Search Console query data rather than translated English terms, and a per-page brief covering intent, headings and the entities to cover. Machine translation published without review is named in Google's spam policies as scaled content abuse, so it is not a shortcut I will build for you.

Indexation of a new Arabic tree usually begins within days once the cluster is valid and the URLs are in the sitemap. Rankings are slower and depend on your vertical, though Arabic UAE queries are frequently less contested than their English equivalents simply because so few businesses publish Arabic content at all. Anyone promising a date or a position is guessing. What is verifiable at any point is whether your Arabic URLs are indexed, whether the cluster is intact, and which queries already produce impressions.

Three things beyond ordinary quality. It works in Arabic as a first-class layout rather than a mirrored afterthought. It is fast on a UAE mobile network specifically, which means field data measured here rather than a lab score from another continent. And it respects local commercial reality: UAE number formats, WhatsApp as a primary contact route, region-appropriate business hours, and the consent and payment requirements that apply here. Design that ignores those reads as imported, and imported is the impression a bilingual site exists to remove.

No, and nobody can. Rankings are decided by a system neither of us controls, against competitors who are also working. What I commit to is method and verification: a correct hreflang cluster you can check with the commands in this post, Arabic pages that are genuinely indexed rather than merely advertised, keyword targets derived from real query data, and Core Web Vitals measured per locale. Those are the inputs that make ranking possible. Anyone selling the output as a guarantee is either misinformed or planning to blame the algorithm later.

// OPEN TO WORK

Hiring, or building something that needs an engineer?