How I Made a Four-App Monorepo Work in GitHub Codespaces
How I made Portfolio, Studio, Admin, and Auth reproducible in one private Codespace with cached setup, scoped secrets, and per-app dev commands.
- GitHub Codespaces
- Developer Experience
- Monorepo
- Next.js
- Security
A development environment for four products
A monorepo is convenient only if getting into it is predictable. This repository has four independently deployed Next.js applications: Portfolio, Studio, Admin, and Auth. They share packages and tooling, but they do not all need to run at once.
I wanted a Codespace to prepare the workspace consistently, make it easy to start one chosen app, and avoid turning local setup into a second place where long-lived secrets are stored.
One path for setup and refresh
The dev container uses Node.js 22 and pins pnpm to 10.32.1. On creation, it prepares the workspace; on resume, it repeats the same preparation so dependency and route-type state match the current branch.
The install uses the frozen lockfile and prefers cached packages. The runtime check keeps an already-installed matching pnpm instead of replacing it. The setup also generates Next.js route types for each app, so the editor has current route information after a branch change.
This is intentionally preparation, not a full verification run. It does not build or test every application on startup; those remain separate quality gates.
Start only what I need
Each client has its own Codespaces command:
pnpm dev:codespaces:portfolio
pnpm dev:codespaces:studio
pnpm dev:codespaces:admin
pnpm dev:codespaces:auth
The wrapper derives the app origins from the Codespace name and forwarding domain, then starts only the selected workspace. Ports 3000 through 3003 map to the four clients and are private by default.
That makes the four-app structure available without requiring four development servers to run together. It also keeps the return URLs and cross-product links pointed at the forwarded origins rather than localhost.
Keep secrets scoped to the process
Codespaces secrets are available at runtime, not written into .env.local. The wrapper maps only the selected app's explicitly allowed secrets into its process, then removes the CODESPACES_* source variables before starting Next.js.
The built-in Codespaces GITHUB_TOKEN is reserved for repository access. Provider features use a separate app token, so repository access and application integrations do not quietly share the same credential.
This boundary matters most for privileged values. For example, the service-role key is mapped only for Studio or Admin, never Portfolio or Auth. The wrappers make that separation part of the startup path rather than relying on someone to remember which variables belong in which terminal.
What this setup does and does not solve
A Codespace can reduce environment drift and remove repeated setup steps, but it does not replace tests, builds, or production configuration. It still depends on the required Codespaces secrets being configured, and the apps still need their own focused checks before changes are shipped.
The setup also has a real resource cost: the configured minimum is two CPUs, 8 GB of memory, and 32 GB of storage. I treat that as a deliberate tradeoff for running a substantial workspace, and stop the Codespace when it is idle.
The useful result is a repeatable path from opening the repository to running one client, with private forwarded ports and app-specific runtime secrets. That is the scope I wanted: a consistent development environment, without pretending that startup alone proves the system is correct.