# create-apollo-monorepo

Scaffold a pnpm monorepo where your custom frontend lives alongside Apollo CMS
mounted as a **git submodule** backend (read-only — pull updates only).

## Usage

```bash
npx create-apollo-monorepo my-site
```

With flags:

```bash
npx create-apollo-monorepo my-site \
  --frontend-name "@my-site/frontend" \
  --db "postgresql://user:pass@localhost:5432/my-site" \
  --url "http://localhost:3000" \
  --locale th
```

## Result

```
my-site/
├── apps/
│   ├── frontend/          ← @my-site/frontend (Next.js skeleton)
│   └── backend/           ← git submodule → apollo-cms
├── package.json           ← root workspace
├── pnpm-workspace.yaml
├── .env.local             ← shared dev env
├── .gitmodules            ← submodule config
└── .gitignore
```

## Routing modes

### Single-origin (default)

Both apps share one public origin (the frontend). The frontend's
`next.config.ts` rewrites these paths to the backend so `/_next/*` doesn't
collide:

| Browser path                         | Goes to              |
| ------------------------------------ | -------------------- |
| `/`, your custom routes              | `apps/frontend`      |
| `/admin/*`                           | `apps/backend` (admin pages **and** their JS chunks) |
| `/api/auth/*`, `/api/v1/*`, `/api/admin/*`, `/api/email/*`, `/api/health`, `/api/mcp`, `/api/editing-presence/*` | `apps/backend` |
| `/uploads/*`                         | `apps/backend` (media) |

Apollo CMS reads `APOLLO_ASSET_PREFIX` (default `/admin`) and serves its built
JS under `<prefix>/_next/static/`. Because the prefix coincides with the admin
path, a single `/admin/:path*` rewrite covers both pages and chunks — no
separate asset rewrite is emitted. The frontend **must not** define routes at
`/admin`, `/api/auth`, `/api/v1`, etc.

You can override the prefix:

```bash
npx create-apollo-monorepo my-app --admin-prefix /cms
```

When the prefix is anything other than `/admin`, the scaffold also emits a
matching `<prefix>/:path*` rewrite for backend chunks.

### Separate origins (fallback)

Pass `--admin-prefix none` (or `off`/`false`/`disabled`) to skip the rewrite
wiring. The backend runs at `http://localhost:3000` and the frontend at
`http://localhost:3001`. Useful when you'd rather deploy them on separate
subdomains (e.g. `cms.example.com` + `example.com`).

## Flags

| Flag                       | Default                                 | Description                          |
| -------------------------- | --------------------------------------- | ------------------------------------ |
| `--frontend-name <name>`   | `@<dir>/frontend`                       | Frontend `package.json` name         |
| `--backend-url <url>`      | `https://github.com/5Lab-Group-Co-Ltd/apollo-cms.git` | Submodule git URL          |
| `--backend-branch <name>`  | `main`                                  | Submodule branch to track            |
| `-d, --db <url>`           | _(prompted)_                            | `DATABASE_URL` for backend           |
| `-u, --url <url>`          | `:3001` single-origin / `:3000` separate | `NEXT_PUBLIC_SITE_URL`               |
| `-l, --locale <code>`      | `en`                                    | `NEXT_PUBLIC_DEFAULT_LOCALE`         |
| `--admin-prefix <path>`    | `/admin`                                | Single-origin admin/asset namespace; `none` to disable. Alias: `--asset-prefix` |
| `--skip-install`           | off                                     | Don't run `pnpm install`             |
| `--skip-submodule`         | off                                     | Don't add the git submodule          |
| `-h, --help`               | —                                       | Show help                            |

## After install

```bash
cd my-site
pnpm backend:setup   # push schema + seed apollo-cms
pnpm dev             # frontend :3001 + backend :3000 in parallel
```

In single-origin mode open `http://localhost:3001/admin` (the frontend port);
the rewrite proxies to the backend and Better Auth's cookies/origins line up
because `NEXT_PUBLIC_SITE_URL` and the backend's `trustedProxyHeaders` are wired
to the public origin.

## Updating the backend

```bash
pnpm backend:update  # git submodule update --remote --merge apps/backend
```

Don't edit files inside `apps/backend` — open issues / PRs against the
`apollo-cms` repository upstream.

### Scaffolds created before v0.9.991

Two entries were missing from older scaffolds, and `pnpm dev` fails during
`plugins:build` without them:

```
error: Could not resolve: "ioredis"
error: Could not resolve: "@opentelemetry/api"
```

`apps/backend/plugins/*` has to be listed in the root `pnpm-workspace.yaml`
(apollo-cms's own workspace file is ignored once the submodule is a package of
an outer workspace, so the built-in plugins' dependencies never install), and
`@opentelemetry/api` has to be a root devDependency (bun's bundler resolves
`next`'s guarded `require()` of this optional peer, which pnpm — unlike bun —
does not install). Patch an existing scaffold with:

```yaml
# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'apps/cms-plugins/*'
  - 'apps/backend/plugins/*'   # add this
```

```jsonc
// package.json
"devDependencies": {
  "concurrently": "^9.0.0",
  "@opentelemetry/api": "^1.9.0"   // add this
}
```

then re-run `pnpm install`.

## Deploying to production

The scaffold pins `packageManager: "pnpm@11.0.0"` so Corepack picks the right
pnpm. If your CI / deploy host installs pnpm separately, **make sure it's
pnpm 11+** — pnpm 11 sets `strictDepBuilds: true` by default and reads the
allow-list from `allowBuilds` in `pnpm-workspace.yaml`. pnpm 10 silently
ignores `allowBuilds`; pnpm 11 silently ignores the deprecated
`onlyBuiltDependencies`. Mixing the two leads to `sharp` (and `esbuild`,
`@swc/core`, …) skipping their postinstalls, which surfaces at runtime as:

```
Failed to load external module sharp-<hash>:
  Error: Cannot find module 'sharp-<hash>'
```

The scaffold's `pnpm-workspace.yaml` already lists the binaries Apollo CMS
needs in `allowBuilds`. If you add a new native dep, append it there.
