From Two Repos to One: Migrating bfpinvest.com to a Monorepo
How we restructured two separate GitHub repositories into a single monorepo, reconfigured three Vercel projects, and set up path-scoped build triggers.
We recently consolidated two GitHub repositories — lockezhou18/bfp (the Next.js marketing site) and lockezhou18/bfp-invest (company ops and tooling) — into a single monorepo. Notes on what we did and what we ran into.
Why a Monorepo?
The split made sense early on. The website was a standalone Next.js project; the ops tooling (Prometheus exporter, Vercel DNS manager, credential aggregator) lived separately. But as the project grew, the friction mounted:
- PRs that spanned both repos required coordination across two repositories
- No shared git history made it hard to trace “what changed around the same time as X”
- Vercel was pointing at
lockezhou18/bfp— a repo that had nothing to do with the company’s operational layer
The tipping point was adding a backend: we wanted Supabase, API routes, and eventually authenticated tooling. That needed a shared workspace, not two separate codebases.
The Structure
The new layout:
bfp-invest/
├── web/ # Next.js 15 — www.bfpinvest.com
├── blog/ # Astro — blog.bfpinvest.com (this site)
├── services/ # Future Python workers
├── tools/ # bfp_auth.py, vercel_dns.py, exporter.py
├── ops/ # company-profile.md, DNS runbooks
└── tests/e2e/ # Future Playwright tests
web/ and blog/ are fully independent deployables — different frameworks, different package.json files, different Vercel projects. The monorepo doesn’t force them to share dependencies; it just gives them a shared git history and a single place to land PRs.
Pulling in the Old Repo
We used git subtree to bring lockezhou18/bfp history into bfp-invest/web/ without losing commits:
# In bfp-invest/
git remote add bfp https://github.com/lockezhou18/bfp.git
git fetch bfp
git merge -s ours --no-commit --allow-unrelated-histories bfp/main
git read-tree --prefix=web/ -u bfp/main
git commit -m "refactor: pull lockezhou18/bfp into web/ subtree"
This replays the full history from the old repo into the web/ subdirectory. The old repo now lives at lockezhou18/bfp-archive.
Reconfiguring Vercel
The tricky part. Three projects (bfp, bfp-mvp, bfp-work) all pointed at lockezhou18/bfp. We needed to:
- Reconnect each project to
lockezhou18/bfp-invest - Set
rootDirectory: webso Vercel builds from the right subdirectory - Add an ignored build step so pushes to
tools/orops/don’t trigger a deploy
The Vercel API doesn’t let you update link via PATCH /v9/projects/{id} — that returns a bad_request: should NOT have additional property link error. You have to disconnect first:
# Disconnect from old repo
DELETE /v9/projects/{id}/link
# Reconnect to new repo
POST /v9/projects/{id}/link
{
"type": "github",
"repo": "lockezhou18/bfp-invest",
"repoId": 1217681571
}
Then set rootDirectory and commandForIgnoringBuildStep via a separate PATCH:
PATCH /v9/projects/{id}
{
"rootDirectory": "web",
"commandForIgnoringBuildStep": "git diff HEAD^ HEAD --quiet -- web/"
}
The counterintuitive part: git diff --quiet exits 0 when there are no changes — which Vercel treats as “skip the build.” So a push that only modifies tools/ will make git diff HEAD^ HEAD --quiet -- web/ exit 0 (no web/ changes) and Vercel skips. A push that touches web/ exits 1 (changes exist) and Vercel builds. This is exactly what you want, but the logic feels backwards at first.
We also took the opportunity to clean up: bfp-mvp (a stale project with messy deploy history from the old repo) was deleted and recreated as bfp-staging. bfp-work was deleted entirely.
What Vercel Shows as “CANCELED”
One thing that confused us: deploys that were skipped by the ignored build step show up as CANCELED in the Vercel API — not SKIPPED. This is expected behavior. Don’t panic when you see it.
Result
- One repo, one PR flow, shared git history
www.bfpinvest.comandblog.bfpinvest.comboth deploy frombfp-investwith path-scoped triggers- The old
lockezhou18/bfprepo is preserved atlockezhou18/bfp-archivefor reference - TypeScript build error in the evaluate page (pre-existing, uncovered by the migration) was caught and fixed as part of the process
The monorepo isn’t magic — it’s just a better container for work that belongs together.