Multi-Domain Monorepos

On this page

Multi-Domain Monorepos

Running more than one independent site out of a single git repository - say, a marketing site and a separate docs site, or a handful of product sites that happen to share one repo for convenience - needs nothing special from bxSites. Each site is just its own project root: its own bxsites.yaml plus its own content folder, built, served, and deployed independently by pointing bxSites at that one folder. A "monorepo with multiple domains" is simply a repository containing several of these project roots side by side.

The pattern

bxsites.yaml's own location is always wherever the CLI's explicit project root points - the current working directory, or an explicit --projectRoot=<path> (or a bare positional path). There's no upward directory search and no separate concept of a "repo root" versus a "project root" - every verb just resolves one project root per invocation and looks for bxsites.yaml (or .yml/.toml/.json) directly inside it. A monorepo with several domains is nothing more than a repository that contains several of these, each independently complete:

my-monorepo/
├── marketing/
│   ├── bxsites.yaml        ← source: .  (or docs - marketing's own choice)
│   ├── index.md
│   ├── about.md
│   └── .theme/               ← marketing's own theme override, if any
└── docs-site/
    ├── bxsites.yaml        ← source: docs
    └── docs/
        ├── index.md
        ├── guides/
        └── .theme/            ← docs-site's own theme override, if any

Each of marketing/ and docs-site/ above is a complete, self-contained bxSites project - its own bxsites.yaml, its own content root (resolved by that project's own source key, or auto-detection - see Content Source), and its own .theme/.themes override living inside that content root. Nothing here is new or monorepo-specific; it's the same single-project-root behavior bxSites has always had, just with more than one project root checked into the same repository.

Nest them however suits the repo - siblings at the root (as above), under a shared sites/ parent, or wherever else makes sense; bxSites only cares about the path you hand it via --projectRoot, not where that path sits relative to the rest of the repo.

Building one domain

Point --projectRoot (or a bare positional path) at the project root you want to build - every verb accepts it, not just build:

bxSites build --projectRoot=marketing
bxSites build --projectRoot=docs-site

Each command resolves its own bxsites.yaml, its own content root, and its own theme override entirely independently - building marketing/ never touches docs-site/'s own site/ output, config, or theme, and vice versa. There's no shared build step and no cross-domain ordering requirement; build whichever domains changed, in whatever order you like.

Previewing one domain

serve works the same way - it previews exactly one project root at a time:

bxSites serve --projectRoot=docs-site

To preview a different domain, stop serve and run it again with a different --projectRoot (or from a different working directory). Two domains that both need live previewing at once just need two separate serve processes, each with its own --projectRoot and (if run simultaneously) its own --port.

Building every domain in CI

Since each domain is an independent bxSites build --projectRoot=<path> invocation, CI just loops over the list of project roots:

name: Build sites
on: [push]
jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        project: [marketing, docs-site]
    steps:
      - uses: actions/checkout@v4
      - name: Install BoxLang + bx-sites
        run: |
          /bin/bash -c "$(curl -fsSL https://install.boxlang.io)"
          export BOXLANG_INSTALL_HOME="/usr/local/boxlang"
          export PATH="/root/.boxlang/bin:/usr/local/bin:$PATH"
          install-bx-module bx-sites
      - name: Build ${{ matrix.project }}
        run: bxSites build --projectRoot=${{ matrix.project }}
      - uses: actions/upload-artifact@v4
        with:
          name: site-${{ matrix.project }}
          path: ${{ matrix.project }}/site/

A plain bash loop works just as well outside a matrix build, and makes it easy to fail the whole job the moment any one domain fails to build:

for project in marketing docs-site; do
	echo "Building $project..."
	bxSites build --projectRoot="$project" || exit 1
done

Each domain's own site/ output is deployed the same way a single-site project's is - see Deployment - just once per domain, pointed at that domain's own deployments/*.json and its own <project>/site/ folder.

No cross-domain collision

Because .theme/.themes live inside each domain's own content root (see Content Source: Where a theme override lives) rather than at a bare, shared project root, two domains in the same monorepo can each carry their own theme override with zero risk of one shadowing the other - marketing/.theme/ and docs-site/docs/.theme/ are two entirely separate folders, resolved independently by each domain's own build. The same goes for everything else project-scoped - bxsites.yaml, deployments/*.json, docs/versions/, docs/i18n/ - each domain's own copy is exactly that domain's own, with nothing shared unless you choose to script the sharing yourself (for example, copying a common extraCss file into each domain before building).

Edit this page Download Markdown Last updated Sep 21, 2026, 5:00:33 PM