create-apollo-suite-monorepo
v1.0.1479
Published
Scaffold a monorepo with a frontend app and Apollo CMS as a git submodule backend (single-origin via Next.js rewrites + assetPrefix)
Maintainers
Readme
create-apollo-suite-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
npx create-apollo-suite-monorepo my-siteWith flags:
npx create-apollo-suite-monorepo my-site \
--frontend-name "@my-site/frontend" \
--db "postgresql://user:pass@localhost:5432/my-site" \
--url "http://localhost:3000" \
--locale thResult
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
└── .gitignoreRouting 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:
npx create-apollo-suite-monorepo my-app --admin-prefix /cmsWhen 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-suite.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 |
| --apps <slug,slug> | (asked) | Suite apps to install besides the mandatory ones; "" for mandatory only. See Suite apps |
| --skip-install | off | Don't run pnpm install |
| --skip-submodule | off | Don't add the git submodule |
| -h, --help | — | Show help |
After install
cd my-site
pnpm backend:setup # push schema + seed apollo-cms
pnpm dev # frontend :3001 + backend :3000 in parallelIn 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.
Suite apps
The scaffold writes apollo.apps.json at the project root: the suite apps the
project installs besides the mandatory ones. An app that is not listed gets no
tables, no screens and no jobs.
--apps <slug,slug>— written as given, after checking it against the backend that was just cloned: every slug must be an app there, and every app it requires must be listed too. A missing required app is an error, never added for you.- No
--apps, in a terminal — the installer lists the apps in the cloned backend (read from itsapps/*/plugin.json, never a list inside the installer) and asks. - No
--apps, not a terminal, or--skip-submodule— mandatory apps only.
.env.local gets APOLLO_APPS_FILE=../../apollo.apps.json (resolved against
apps/backend), and the root package.json gets an apl script:
pnpm apl apps # every app and its state
pnpm apl install <slug...> # add apps — runs the upgrade that creates their tables
pnpm apl uninstall <slug> # leave an app out — its tables and data are kept
pnpm apl doctor # do the file, the apps and the database agree?Before the database has been set up once, edit the list by hand and run
pnpm backend:setup; apl install needs the tables setup creates.
Updating the backend
pnpm backend:update # git submodule update --remote --merge apps/backend
# + node apps/backend/scripts/sync-root-workspace.mjs + pnpm installDon't edit files inside apps/backend — open issues / PRs against the
apollo-cms repository upstream.
Root pnpm-workspace.yaml mirrors the backend's overrides and patches
pnpm reads only the root pnpm-workspace.yaml; the submodule's own copy
is ignored once apps/backend is a package of the outer workspace. The
scaffold (and pnpm backend:update) therefore run
node apps/backend/scripts/sync-root-workspace.mjs, which copies the
backend's security overrides and patchedDependencies into the root file
(patch paths rewritten to apps/backend/patches/…). Your own root overrides
are kept; backend keys win on conflict.
Without the mirror the drizzle-kit patch never applies, and every
drizzle-kit push / apl install re-adds each NULLS NOT DISTINCT unique
constraint and prompts to truncate any non-empty table that owns one —
in CI (no TTY) the push aborts.
It also writes the backend's own next, react and react-dom specifiers
into the root overrides, so every workspace package — your frontend and
every apps/cms-plugins/* suite app included — resolves the same copy the
backend does, and the next pnpm backend:update moves them for you. Suite
apps are bundled into the backend's Next build; a second next (or React)
makes pnpm install a second next-safe-action, and typecheck fails with
TS2345 across every useAction call (apollo-suite #1915, #1916). The cost is
that your frontend follows the backend's next/react too.
One-time step for an existing consumer, after the first backend:update that
brings this in: commit the rewritten root pnpm-workspace.yaml and
pnpm-lock.yaml, and check pnpm why next-safe-action shows ONE copy.
Adding node apps/backend/scripts/sync-root-workspace.mjs --check to CI makes
a submodule bump that skipped backend:update fail loudly instead.
Scaffolds created before sync-root-workspace
Run it once by hand, then reinstall:
node apps/backend/scripts/sync-root-workspace.mjs && pnpm installand add node apps/backend/scripts/sync-root-workspace.mjs to your
backend:update script between the submodule update and pnpm install.
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:
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'apps/extensions/*'
- 'apps/backend/plugins/*' # add this// package.json
"devDependencies": {
"concurrently": "^9.0.0",
"@opentelemetry/api": "^1.9.0" // add this
}then re-run pnpm install.
Scaffolds created before --apps
They keep working unchanged: with no apollo.apps.json and no
APOLLO_APPS_FILE, the backend installs every app. To choose apps in one of
them, after pnpm backend:update has brought in a backend with
scripts/apl.ts:
Add the script to the root
package.json:"apl": "pnpm --filter ./apps/backend exec bun run scripts/apl.ts"Add
apollo.apps.json.apl-tmpto.gitignore.Run
pnpm apl apps. Createapollo.apps.jsonat the project root listing every app it shows asinstalled—{ "apps": ["<slug>", "<slug>"] }— so nothing changes yet. Leave out the ones markedmandatory.Add
APOLLO_APPS_FILE=../../apollo.apps.jsonto the root.env.local. ⚠️ Do steps 3 and 4 in this order: without the variable,aplwrites toapps/backend/apollo.apps.json, inside the submodule.pnpm apl doctorshould report nothing. Thenpnpm apl uninstall <slug>for each app the project does not use — its tables and data are kept.
Deploying to production
The scaffold pins packageManager: "[email protected]" (the version lives in
pnpm-policy.mjs), so Corepack or pnpm itself switches to it. If your CI /
deploy host installs pnpm separately, make sure it's pnpm 12+ — pnpm sets
strictDepBuilds: true by default and reads the allow-list from allowBuilds
in pnpm-workspace.yaml; pnpm 10 silently ignores allowBuilds, and pnpm 11+
silently ignores the deprecated onlyBuiltDependencies. Once the version is
pinned, pnpm 12 fails (ERR_PNPM_UNRECOGNIZED_WORKSPACE_SETTINGS) on any key
in pnpm-workspace.yaml it does not know. No .npmrc is generated: pnpm 11+
reads it only for auth and registry, so the install settings it used to carry
(engineStrict, autoInstallPeers, strictPeerDependencies,
publicHoistPattern, shellEmulator) live in pnpm-workspace.yaml.
⚠️ pnpm 12 also ignores a registry token written as an environment variable
in a project .npmrc. It warns environment variables are not expanded in
repository-controlled registry credentials and installs without the token.
On a CI or deploy host that installs from a private registry, put the token in
the user-level ~/.npmrc or pnpm's global config instead.
Mixing up the build allow-list 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.
Node.js policy
Use Node 24.22.1 (the latest Node 24 release verified on 2026-10-23). Run nvm install && nvm use from the project root. Later stable 24.x patches are accepted; Node 22, 25, 26 and prereleases are rejected. CI reads the exact version from .nvmrc for reproducibility. Update .nvmrc, .node-version, both package engine ranges and installer-monorepo/node-policy.mjs together when adopting a newer Node 24 release. The installer copies this policy and pins into generated projects. GitHub Actions may use their own bundled Node runtime, separate from project commands.
