Building a Bilingual English-Arabic (RTL) Website with Next.js
If you build websites for clients in the UAE, Saudi Arabia or Bahrain, sooner or later someone will ask: "Can we have it in Arabic too?"
Adding Arabic is not just translating text. The whole layout flips from right to left, fonts change, icons need mirroring, and Google needs to know which page is for which language. This guide shows the setup that works well in Next.js (App Router).
1. Use a locale segment in your routes
Put every page under a [locale] folder:
app/
[locale]/
layout.tsx
page.tsx
services/page.tsx
middleware.ts
This gives you clean URLs: /en/services and /ar/services. Separate URLs per language are what search engines want.
For translations and locale routing, a library such as next-intl handles middleware, message files and helpers for you, so you don't have to write your own.
2. Set lang and dir on the <html> tag
This is the single most important RTL step. The browser uses dir="rtl" to flip text direction and the default layout flow.
// app/[locale]/layout.tsx
import { Inter, Cairo } from "next/font/google";
const inter = Inter({ subsets: ["latin"], variable: "--font-inter" });
const cairo = Cairo({ subsets: ["arabic", "latin"], variable: "--font-cairo" });
export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const dir = locale === "ar" ? "rtl" : "ltr";
return (
<html lang={locale} dir={dir} className={`${inter.variable} ${cairo.variable}`}>
<body className={locale === "ar" ? "font-arabic" : "font-sans"}>
{children}
</body>
</html>
);
}
Tip: use a proper Arabic font such as Cairo, Tajawal or Noto Sans Arabic. Latin-only fonts make Arabic look broken or fall back to an ugly system font.
3. Stop using left and right in your CSS
Instead of physical directions, use logical properties. They adapt automatically when dir changes.
- Instead of
margin-left, usemargin-inline-start - Instead of
padding-right, usepadding-inline-end - Instead of
text-align: left, usetext-align: start - Instead of
left: 0, useinset-inline-start: 0
If you use Tailwind (v3.3+), the same idea is available as utility classes:
<div class="ms-4 pe-6 text-start"> ... </div>
ms-* and me-* are margin start/end, ps-* and pe-* are padding start/end. Write your components this way from day one and you won't need a separate RTL stylesheet.
4. Mirror only the icons that need it
Arrows, "next" and "back" chevrons, and progress indicators should flip in RTL. Logos, phone icons, checkmarks and play buttons should not.
With Tailwind's rtl: variant:
<ArrowRight class="rtl:rotate-180" />
5. Tell Google about both languages (hreflang)
Each page should point to its translated version. In Next.js you can do this with the Metadata API:
// app/[locale]/page.tsx
import type { Metadata } from "next";
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}): Promise<Metadata> {
const { locale } = await params;
return {
metadataBase: new URL("https://www.example.com"),
alternates: {
canonical: `/${locale}`,
languages: {
en: "/en",
ar: "/ar",
"x-default": "/en",
},
},
};
}
Also list both language versions in your sitemap.xml, and make sure each page has its own translated <title> and meta description.
6. Common mistakes I see
- Machine-translated Arabic copy. Native readers notice immediately. Have a real speaker review it.
- Hard-coded left / right values hidden inside third-party components.
- Numbers and mixed text. A sentence mixing Arabic and English (like a brand name) can render in the wrong order. Wrap the English part in
<bdi>or usedir="auto"on user-generated content and form inputs. - Forgetting to test long Arabic text. Arabic words and sentences have different lengths, so fixed-width buttons and cards can break.
- No language switcher state. Keep the user on the same page when they switch (
/en/servicesβ/ar/services), not back to the home page.
Quick checklist
- [ ]
[locale]route segment - [ ]
langanddirset on<html> - [ ] Arabic-capable font loaded
- [ ] Logical CSS properties (or Tailwind
ms-/me-/ps-/pe-) - [ ] Directional icons mirrored
- [ ]
hreflang+ canonical per page - [ ] Both languages in
sitemap.xml - [ ] Arabic copy reviewed by a native speaker
Wrapping up
Once the foundation is right (locale routes, dir, logical CSS and hreflang), adding a new language later becomes mostly a content task instead of a rewrite.
I'm [Your Name], founder of CodEarn, where we build websites, web apps, mobile apps and AI automation for businesses in the Gulf region. If you are planning a bilingual website, feel free to reach out.
What was the trickiest part of RTL for you? Let me know in the comments.
Comments
No comments yet. Start the discussion.