DEV Community

Jakub
Jakub

Posted on

Running 5 International Domains from One Codebase (React SPA + Lovable)

We run an AI photo animation product across 5 country domains, all from a single React codebase. No monorepo, no microservices, no framework gymnastics. Just one Lovable project serving zivafotka.cz, zivafotka.sk, zywafotka.pl, alivephoto.online, and lebendigfoto.de.

Here's how it works and what we learned making it happen.

The problem

We built Ziva Fotka as a Czech product. Upload a photo, AI animates it into a short video with natural movement. Simple concept, works well. Then we wanted to go international.

The naive approach would be one codebase per country. Five repos, five deploys, five sets of bugs to fix. That's a maintenance nightmare for a small team. We needed one codebase that adapts to whichever domain it's running on.

Domain-based locale detection

The key insight: the domain IS the locale selector. No language dropdown, no cookies, no URL prefixes like /en/ or /de/. If you're on zivafotka.cz, you get Czech. If you're on lebendigfoto.de, you get German. Clean and obvious.

// Simplified locale detection
function getLocale(): Locale {
  const hostname = window.location.hostname;

  if (hostname.includes('zivafotka.cz')) return 'cs';
  if (hostname.includes('zivafotka.sk')) return 'sk';
  if (hostname.includes('zywafotka.pl')) return 'pl';
  if (hostname.includes('lebendigfoto.de')) return 'de';
  return 'en'; // alivephoto.online and fallback
}
Enter fullscreen mode Exit fullscreen mode

This runs once on app init and feeds into a React context that every component can consume. No runtime overhead, no flicker, no hydration mismatch.

Routing: same paths, different languages

Each domain has its own URL structure that makes sense in that language:

  • CZ: /srovnani/ziva-fotka-vs-myheritage
  • SK: /porovnanie/ziva-fotka-vs-myheritage
  • PL: /porownanie/zywa-fotka-vs-myheritage
  • DE: /vergleich/lebendiges-foto-vs-myheritage
  • EN: /comparison/alive-photo-vs-myheritage

Routes are defined per locale in a central config. The React Router setup maps locale-specific paths to shared page components. The component receives locale as a prop and renders the right content.

// Route config per locale
const routes = {
  cs: { comparison: '/srovnani', pricing: '/cenik' },
  sk: { comparison: '/porovnanie', pricing: '/cennik' },
  pl: { comparison: '/porownanie', pricing: '/cennik' },
  de: { comparison: '/vergleich', pricing: '/preise' },
  en: { comparison: '/comparison', pricing: '/pricing' },
};
Enter fullscreen mode Exit fullscreen mode

Sitemaps: one per domain

This was the trickiest SEO piece. Each domain needs its own sitemap with its own URLs. We generate five separate sitemap files:

  • sitemap-cz.xml for zivafotka.cz
  • sitemap-sk.xml for zivafotka.sk
  • sitemap-pl.xml for zywafotka.pl
  • sitemap-en.xml for alivephoto.online
  • sitemap-de.xml for lebendigfoto.de

Each sitemap only contains URLs for that specific domain. A sitemap.xml index file references all five. The robots.txt points to the index sitemap.

Critical detail: every URL in every sitemap includes hreflang annotations pointing to the equivalent page on all other domains. This tells Google "these five pages are the same content in different languages."

<url>
  <loc>https://zivafotka.cz/srovnani/ziva-fotka-vs-myheritage</loc>
  <xhtml:link rel="alternate" hreflang="cs"
    href="https://zivafotka.cz/srovnani/ziva-fotka-vs-myheritage"/>
  <xhtml:link rel="alternate" hreflang="sk"
    href="https://zivafotka.sk/porovnanie/ziva-fotka-vs-myheritage"/>
  <xhtml:link rel="alternate" hreflang="pl"
    href="https://zywafotka.pl/porownanie/zywa-fotka-vs-myheritage"/>
  <xhtml:link rel="alternate" hreflang="de"
    href="https://lebendigfoto.de/vergleich/lebendiges-foto-vs-myheritage"/>
  <xhtml:link rel="alternate" hreflang="en"
    href="https://alivephoto.online/comparison/alive-photo-vs-myheritage"/>
</url>
Enter fullscreen mode Exit fullscreen mode

SEO: dynamic meta per domain

Every page uses a custom useSEO hook that sets the right meta tags based on the current locale and domain:

  • Title and description in the local language
  • Canonical URL pointing to the current domain (not a "master" domain)
  • Open Graph tags with locale-specific content
  • JSON-LD schema with the correct publisher for each domain

This is where a lot of multi-domain setups go wrong. If your canonical tags all point to the .com domain, Google ignores your country-specific pages. Each domain must be treated as authoritative for its locale.

What we got wrong (and fixed)

Wrong brand name per locale. We initially used "Ziva Fotka" everywhere. But the Polish site is "Zywa Fotka" and the German one is "Lebendiges Foto." Every reference to the product name needs to be locale-aware, not just the UI copy.

Forgetting footers. Comparison page links in the footer were hardcoded to Czech paths. On the Polish domain they led to 404s. Footer navigation has to be locale-routed like everything else.

Sitemap submission per property. Each domain is a separate property in Google Search Console. You need to submit the domain-specific sitemap to each property individually. We missed alivephoto.online for weeks.

The numbers

Running five domains instead of one has a real impact on discoverability. Czech users searching "ozivit fotku" land on zivafotka.cz. Polish users searching "ozywic zdjecie" land on zywafotka.pl. Same product, but each market finds it through natural search in their own language.

Our CTR in Google Ads runs 13-16% across CZ and SK domains, significantly above typical display benchmarks. The domain matching the user's language builds immediate trust.

Why Lovable makes this work

We built everything in Lovable, which gives us a React SPA with Supabase backend. The single-project architecture means one deploy covers all five domains. DNS and domain routing handles the rest.

If you're building with Lovable and considering multiple markets, this pattern scales well. The key principles: detect locale from domain (not URL), generate per-domain sitemaps with hreflang cross-references, and make every piece of content locale-aware, including things you'd never think of like footer links and schema markup.

The codebase for all five domains of Ziva Fotka / Alive Photo lives in a single Lovable project. Check out the live sites to see it in action.

We're building more products at Inithouse, each with its own scaling challenges. Be Recommended checks if AI assistants recommend your business. Watching Agents runs AI prediction agents. Different problems, same build-fast philosophy.

Top comments (0)