The DatoCMS Blog

Building MultiLaunch (Part 3/3): The Repo and the DX

Posted on May 6th, 2025 by Ronak Ganatra and Mojtaba Seyedi

TL:DR

This is part 3 of a 3 part series on MultiLaunch - an Astro Theme built by Bejamas using DatoCMS, Astro, and Vercel.

If you're looking for quick links to take it all for a spin, here's what you need 👇

We also had a really nice chat with Mojtaba on his approach to the whole project, so check that out too!


Ok let's get riiiiight into it.

Looking at the repo

MultiLaunch was designed for companies running multiple brand sites. The dev experience needed to match that goal, which is why its built as a monorepo with Astro, DatoCMS, and Vercel. One repo. One CMS. As many brand sites as you need without duplicating code, content models, or deployments.

It had to be DRY. It had to be scalable. It had to let you launch new brands without spinning up new projects or writing new routes.

Which is why, from the start, the answer for Mojtaba was simple: build it as a system, not a one-off template. That meant a monorepo setup, with a shared UI layer, config logic based on slugs, and a CMS that could feed all of it.

At the top level, the repo has:

  • apps/core — for the main site

  • apps/brand — for individual brand sites

  • packages/ui — for shared components and design tokens

Each app is its own Astro instance, but they pull from shared logic and styles in packages/ui. So if you update a button style or fix a bug in a component, that fix rolls out everywhere.

This also means there's no duplication when building new sites. Everything from layouts to typography to SEO logic is shared, unless you explicitly override it.

On the individual brand side of things, each brand deployment is controlled by a BRAND_SLUG env variable. That slug maps directly to a brand entry in DatoCMS.

Here’s how it works:

  1. Create a new brand record in DatoCMS

  2. Create a new Vercel project pointing to apps/brand

  3. Add BRAND_SLUG=some-brand in the env vars

  4. Hit deploy

And just like that, you've got a brand new shiny website up and running for a new brand, complete with consistent branding and structure to match the others.

New site on a custom domain, powered by the same frontend, same schema, same logic. Easy.

You don’t even have to touch your code. You just deploy a new project with a different slug.

Mojtaba Seyedi

When working locally in dev, you do the same thing with .env files. That means no branching. No boilerplate copying. No guessing where things live.

But why Astro?

Now, we're big fans of Astro here at DatoCMS, so this wasn't just a "We love it let's do it with this" kinda story. There's very well-founded reasons for the Bejamas crew opting for Astro.

First. Performance. Astro ships zero JavaScript by default. For mostly-static brand sites, this is a huge win. Pages load fast, Core Web Vitals are great out of the box, and you don’t need to fine-tune hydration.

Second. Flexibility. Astro works with React, Vue, Svelte, Solid... (I mean islands are just dope). You can bring in what you need, where you need it.

Third. File-based routing and localization are straightforward. With minimal setup, each locale can be its own route. And since everything is based on slugs, the routing logic stays clean.

MultiLaunch also uses the official DatoCMS CDA client (@datocms/cda-client) to query content. It’s wrapped in a utility function that handles slugs and locales, so components don’t need to deal with query logic directly.

You pass in the brand slug, the locale, and the query, and get back clean, typed data.

This keeps the Astro pages lean and focused on rendering.

Let's talk under the hood

Here's a quick look into the considerations that really contribute to the overall DX when working with this theme.

But first, a quick look into how the repo is structured.

Build Triggers for smoooooth CI/CD

Once a brand site is live, publishing new content is completely decoupled from the dev team. DatoCMS has native build trigger support, which is hooked up to Vercel deployments.

That means any content update can trigger a rebuild and redeploy, no code pushes needed.

You could be running 5, 10, 50, 100 brand sites, and none of them require individual attention after setup.

Bun over NPM

Also, this project now runs with Bun. It’s faster for installs and plays well with Vercel. And they got a hella cute logo.

I switched to Bun after running into an install issue on Vercel. It just worked. It was faster too, so I stuck with it.

Mojtaba Seyedi

That’s one of those small DX quality-of-life things that adds up when you’re managing many deployments.

UI Packaging

All UI components live in packages/ui. This includes typography, layout sections, buttons, review cards, and form components.

That package is used across both core and brand apps. So if the design system evolves, you’re not updating components in multiple places.

And because everything is schema-driven, the components are flexible. They accept props passed from DatoCMS queries like brand colors, section order, content blocks, and render them accordingly.

Built-in SEO

Each page in MultiLaunch pulls its own metadata from DatoCMS. The CDA client queries include fields like metaTitle, metaDescription, canonical, and ogImage.

_seoMetaTags {
tag
attributes
content
}

Those get passed to a layout-level SEO component, which uses the official DatoCMS <StructuredMeta> component from @datocms/astro.

It’s easy to implement and ensures that every brand site is search-ready out of the box.

Geo-redirects

As a last flex, The system also supports geo-based redirects.

There’s middleware in Astro that detects a user’s country and sends them to the right localized version of the site. For example, a visitor from Germany gets redirected to /de.

// Get user's country from Vercel geo data
const country = request.headers.get('x-vercel-ip-country')?.toLowerCase() || ''
// Map countries to locales (extend this mapping as needed)
let locale = DEFAULT_LOCALE
if (['fr', 'be', 'ch'].includes(country)) {
locale = 'fr'
} else if (['de', 'at', 'ch'].includes(country)) {
locale = 'de'
} else if (['pl'].includes(country)) {
locale = 'pl'
} else if (['cn', 'hk', 'tw'].includes(country)) {
locale = 'zh'
} else if (['sa', 'ae', 'eg', 'iq', 'jo', 'kw', 'lb', 'om', 'qa', 'sy'].includes(country)) {
locale = 'ar'
}
// Redirect to the appropriate locale
return new Response(null, {
status: 302,
headers: {
Location: `/${locale}${pathname === '/' ? '' : pathname}${url.search}`
}
})
}

It’s a small touch, but it makes the whole experience feel polished, especially when you’re managing brands across multiple countries.


To wrap things up, this setup works because it's consistent.

  • One repo

  • One CMS

  • One schema

  • One query pattern

  • One UI system

You don’t reinvent anything when launching a new site. You just plug in new data. Everything else flows from that.

It’s fast to work with, easy to extend, and super clean to manage.

Mojtaba Seyedi

And that’s the point. MultiLaunch was built to scale, not just content-wise, but technically. It’s designed so you don’t have to rebuild the same logic every time.

Bonus point? Performance for the end-user. Because no new frontend project is ever really complete without flexing those token Lighthouse scores 💅

So take it for a spin, and let us know what you're building with it!

Start using DatoCMS today
According to Gartner 89% of companies plan to compete primarily on the basis of customer experience this year. Don't get caught unprepared.
  • No credit card
  • Easy setup
Subscribe to our newsletter! 📥
One update per month. All the latest news and sneak peeks directly in your inbox.
support@datocms.com ©2025 Dato srl, all rights reserved P.IVA 06969620480