A monorepo holds multiple services in one Git repository: apps/web, apps/api, apps/admin, each with its own package.json and build process. Deploying one of them shouldn't require installing dependencies for the other three, but most platforms clone the entire repository and run npm install at the root, which wastes time and storage.
PandaStack supports a rootDir field that changes the build context to a subdirectory. The build process cds into apps/web, runs npm install there, and ignores the rest of the monorepo. This works for npm workspaces, Yarn workspaces, pnpm, Turborepo, and Nx — any structure where services are self-contained.
Monorepo structure
Your repository might look like this:
repo/
package.json (root workspace config)
apps/
web/
package.json
src/
vite.config.js
api/
package.json
src/
packages/
shared-ui/
package.jsonThe apps/web directory is a standalone Vite app. It depends on packages/shared-ui, but PandaStack doesn't need to know that — npm/yarn/pnpm handle workspace dependencies automatically when you run npm install inside apps/web.
Configure pandastack.json for the subdirectory
Create apps/web/pandastack.json:
{
"type": "static",
"language": "nodejs",
"buildCommand": "npm run build",
"outputDir": "dist",
"rootDir": "apps/web"
}The rootDir field tells PandaStack: clone the full repository, then cd into apps/web before running build commands. The outputDir is now relative to apps/web, not the repository root, so it resolves to apps/web/dist.
Deploy via the REST API
Create a project that deploys only the apps/web subdirectory:
curl -X POST https://api.pandastack.io/v1/projects \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slug": "static",
"name": "monorepo-web",
"repositoryName": "acme/monorepo",
"branch": "main",
"autoDeploy": true,
"rootDir": "apps/web"
}'The API field is rootDir, matching the JSON schema. If you're using the deploy button, the query parameter is rootDir or root-directory (both work).
What happens during the build
PandaStack clones the full repository, then runs:
cd apps/web
npm install
npm run buildIf apps/web/package.json references a workspace dependency like @repo/shared-ui, npm resolves it by traversing up to the root package.json and following the workspace configuration. The build gets the shared code without you having to configure PandaStack to install root dependencies first.
Deploy button for a subdirectory
If this is a starter template or open-source project, add a deploy button to apps/web/README.md:
[](https://dashboard.pandastack.io/deploy?repo=acme/monorepo&rootDir=apps/web)The rootDir query parameter seeds the deploy form, and when PandaStack fetches pandastack.json, it reads it from apps/web/pandastack.json (not the repository root) because that's the effective root directory.
Use the CLI
panda login
panda projects create \
--name monorepo-web \
--repo acme/monorepo \
--branch main \
--type static \
--auto-deployThe CLI doesn't have a --root-dir flag yet, so you need to add rootDir via the dashboard (Settings → General) or include it in pandastack.json.
When workspace dependencies break
If your build fails with Cannot find module '@repo/shared-ui', the workspace resolution isn't working. This usually means one of three things:
- 1The root
package.jsonis missing theworkspacesfield. pnpm readspnpm-workspace.yamlinstead, so make sure that file exists.
- 1The shared package isn't installed. Some monorepo setups require running
npm installat the root first, then again in the subdirectory. PandaStack only runs it in the subdirectory. Fix this by adding apostinstallscript toapps/web/package.json:
`json
{
"scripts": {
"postinstall": "cd ../.. && npm install"
}
}
`
- 1The package manager is different locally vs in the build. If you use pnpm locally but PandaStack detects npm (because there's a
package-lock.json), delete the lockfile and commitpnpm-lock.yamlinstead.
Deploy multiple services from the same repo
Create separate projects for apps/web and apps/api:
# Deploy the frontend
curl -X POST https://api.pandastack.io/v1/projects \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slug": "static",
"name": "monorepo-web",
"repositoryName": "acme/monorepo",
"branch": "main",
"rootDir": "apps/web"
}'
# Deploy the backend
curl -X POST https://api.pandastack.io/v1/projects \
-H "Authorization: Bearer $PANDASTACK_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"slug": "container",
"name": "monorepo-api",
"repositoryName": "acme/monorepo",
"branch": "main",
"rootDir": "apps/api"
}'Both projects share the same Git repository but deploy independently. Pushing to main triggers rebuilds for both.
Turborepo and build caching
If you use Turborepo, the turbo.json at the repository root declares build dependencies. When you deploy apps/web, Turborepo knows it depends on packages/shared-ui and builds both in the correct order.
Make sure your buildCommand invokes Turborepo:
{
"buildCommand": "npx turbo run build --filter=web",
"rootDir": "apps/web"
}The --filter=web flag builds only the web app and its dependencies, skipping unrelated services.
Debugging build failures
npm ERR! 404 Not Found - GET https://registry.npmjs.org/@repo%2fshared-ui: The workspace package name is being treated as an npm registry package. Check that the root package.json has "workspaces": ["apps/*", "packages/*"].
Error: Output directory 'dist' not found: The outputDir is relative to rootDir, so "outputDir": "apps/web/dist" is wrong. Use "outputDir": "dist" instead.
Build runs at the repository root instead of the subdirectory: You set rootDir in pandastack.json but forgot to push the file. Verify it's committed to the branch you're deploying.
Performance: does cloning the full repo waste time?
Git clones are fast because they transfer only changed objects. If your monorepo is 500 MB but you push a 10 KB change, the incremental clone takes seconds. The savings from rootDir come from skipping npm install and npm run build for unrelated services, not from avoiding the clone.
If clone times are genuinely a problem (e.g., your repo includes gigabytes of binary assets), use Git LFS or split the monorepo into separate repositories.
References
- [npm workspaces](https://docs.npmjs.com/cli/v10/using-npm/workspaces)
- [pnpm workspaces](https://pnpm.io/workspaces)
- [Turborepo filtering](https://turbo.build/repo/docs/core-concepts/monorepos/filtering)
- [PandaStack monorepo guide](https://docs.pandastack.io/projects)