Musa Yılmaz
·2 min read

Building Admin-Managed SEO Landing Pages in 15 Languages

nextjsseodjangoi18n

For an airport transfer site, we needed dozens of location-based pages like "Istanbul Airport to Taksim Transfer" — in 15 different languages. Managing this with hand-written static pages per language wasn't sustainable: adding a new location meant needing a developer.

Data-driven page generation

The fix was to keep page content in the database instead of in code. We defined a "Location" and "Transfer Route" model in the Django admin; each route record holds title, description, price range, and more, in 15 languages. On the Next.js side, a single dynamic route fetches that data and renders the page:

// app/[locale]/transfer/[slug]/page.tsx
export async function generateStaticParams() {
  const routes = await fetchAllTransferRoutes();
  return routes.flatMap((route) =>
    locales.map((locale) => ({ locale, slug: route.slug }))
  );
}
 
export default async function TransferPage({ params }) {
  const { locale, slug } = await params;
  const route = await fetchTransferRoute(slug, locale);
  return <TransferLandingTemplate route={route} />;
}

Adding a new location is now just filling in a few fields in the admin panel — no deploy required.

Instant updates with ISR

Pages are statically generated at build time via generateStaticParams, but we didn't want content updates to wait for the next build. When a record is saved in Django, we fire a webhook to Next.js's revalidate endpoint:

# Django signal
@receiver(post_save, sender=TransferRoute)
def revalidate_page(sender, instance, **kwargs):
    requests.post(
        f"{NEXTJS_URL}/api/revalidate",
        json={"path": f"/transfer/{instance.slug}"},
        headers={"Authorization": f"Bearer {REVALIDATE_SECRET}"},
    )

This keeps the performance benefits of static generation while content changes go live within seconds.

RTL support for Arabic

Since Arabic is one of the 15 languages, text translation alone isn't enough — the page direction needs to flip too. With next-intl, we set the dir attribute automatically based on locale:

<html lang={locale} dir={locale === "ar" ? "rtl" : "ltr"}>

We also used Tailwind's rtl: variant to flip direction-dependent styles like margins and padding.

Result

With this setup, the company doesn't need me or another developer to add a new city or route — they fill in a few fields in the admin panel, and the page goes live in seconds, SEO-ready, in all 15 languages.