A shared staging site you both see. PRs as the universal "please review this." Approval gates before prod. Plus what happens when you both edit the same file.
You described it perfectly: each of you has a private dev version on your own laptop. You push to a shared staging site you can both see. You review each other's work. When you both agree, staging promotes to production. Sometimes Josh is the approver (frontend territory). Sometimes you are (backend). The system needs to handle both flows.
This is one of the most common patterns in modern dev — formal enough to enforce review, light enough not to drown two people in process. Three rules will get you 90% of the way:
1. Nobody pushes directly to main. Production is sacred. All changes arrive via pull request.
2. Every PR needs at least one approval. By default the other person. GitHub can enforce this — no merge button until you have a 👍.
3. Code owners are auto-assigned. A magic file maps folders to humans. Touching frontend/ auto-requests Josh's review. Touching backend/ auto-requests yours.
All three are legitimate. They differ in how heavy the process is and whether you have a single persistent staging URL or many ephemeral preview URLs.
main, no reviewBoth push to main. CF Pages auto-deploys. No code review, no approval gates. Used when you fully trust each other and the project is so small that ceremony would be silly.
main, preview URLsEach feature gets a branch. Open a PR to merge into main. CF Pages spins up a preview URL per PR — that's how the other person "sees" the change. After approval, merge → prod redeploys.
dev branch as shared stagingFeature branches → PR into dev → both review → merge → dev.site.com updates. You both see the same staging URL evolve. When you're both happy with what's in staging, PR dev → main → both approve → prod redeploys.
Pattern C. It matches exactly how you described wanting to work, and the small amount of extra ceremony is worth it — you get a single persistent dev.site.com URL that's perfect for client previews, and a real two-step approval flow. The whole pattern is what GitHub itself uses for its docs site. It works.
"Pull request" is a confusing name. It's not really a "request to pull." It's: "hey, I've made changes on branch X. Here's the diff. Please review and either merge it into branch Y or tell me what to fix." Plus a comment thread attached.
Mechanically, when Josh wants to add a feature:
That last command opens a PR from his feature branch into dev. You get notified. You click the PR link. You see a diff of his changes + the preview URL CF auto-deployed. You leave comments, request changes, or approve. Once approved, you (or he) clicks Merge.
feature/josh-card-flip → dev
Open the PR on github.com → click the Files changed tab → you see a diff of every change. Hover over any line, click the + button that appears, leave a comment. When done, click Review changes at the top → pick one of: Comment (just feedback, no verdict), Approve (👍 to merge), or Request changes (👎, fix these first).
One of GitHub's nicest features is the CODEOWNERS file. It's a plain text file at .github/CODEOWNERS that maps folder paths to GitHub usernames. When a PR touches a folder, the owner is auto-requested as reviewer.
Josh opens a PR touching frontend/ → GitHub auto-requests YOUR review (he's the author, you're the other set of eyes). Touching backend/ auto-requests his review of yours. No human has to remember "wait, who should look at this?" The file does it.
When combined with the next concept (branch protection rules), CODEOWNERS becomes required — the merge button is greyed out until the code-owner has approved. That's how teams enforce "frontend changes require frontend lead's blessing" without anyone manually policing.
main uncrushable.You don't want anyone (including yourselves at 2am) to accidentally push broken code straight to prod. GitHub's branch protection rules let you set up gates that the merge button respects.
Where to set this: github.com/lovebuilt/your-repo → Settings → Branches → Add rule. Apply it to the main branch (and also dev, to enforce review there too).
Require a pull request before merging — no direct pushes to main. Forces the PR flow.
Require approvals: 1 — at least one human review before merge.
Require review from Code Owners — the auto-assigned CODEOWNER must specifically approve. (Not just any random teammate.)
Dismiss stale reviews when new commits are pushed — if Josh approves your PR, then you push 3 more commits, his approval auto-clears. Forces re-review.
Require status checks to pass — if you have CI (linting, tests), they must be green before merge. Optional but useful once you have CI set up.
By default, GitHub lets repo admins bypass branch protection rules. That defeats the point — you'll be tempted to "just merge it, no review needed this once" at the worst possible time. In the rule, enable "Do not allow bypassing the above settings" so even admins must follow the process.
You raised this directly: Josh might own frontend approval ("his realm"), but you own backend approval. Different PRs need different lead reviewers. CODEOWNERS handles this without anyone having to think about it.
The way it plays out in practice:
You're the AUTHOR. The PR touches frontend/. CODEOWNERS auto-requests Josh. Branch protection says "code owner must approve." Josh is the gate. You can also approve as a courtesy 👍, but his approval is what unlocks the merge button. He drives.
Josh authors, PR touches backend/. Auto-requests you. Your approval is the gate. He can thumbs-up as courtesy, but you're the technical reviewer. You drive.
CODEOWNERS rule chains together: both Josh's and your approvals are required before merge. This is the "we're refactoring how the API talks to the UI, both eyes needed" case.
The beauty: nobody is manually saying "I'm the frontend approver." The repo configuration says it once, and every future PR routes correctly. New collaborators (a designer, a contractor) get added to CODEOWNERS once and they're in the flow.
This will happen. You both work on dev for a while, you both touch portal/index.html, and at merge time git says: "I can't auto-merge this — the same lines were edited differently. Please tell me which version to keep."
Git marks the conflict by inserting visible markers in the file:
The lines between <<<<<<< HEAD and ======= are your version. The lines between ======= and >>>>>>> are Josh's version. You open the file in your editor, delete the markers, keep whichever line(s) you want (or combine them), save, and commit.
1. Edit the file: pick which version to keep (or merge by hand). Delete all the <<<, ===, >>> markers.
2. git add <the-file> — tells git "I resolved this one."
3. git commit — git generates a default merge message like "Merge branch 'feature/josh-purple-headers' into dev". Accept it.
4. git push — back in business.
When a merge conflict happens, VS Code highlights the conflict block and shows clickable buttons: "Accept Current", "Accept Incoming", "Accept Both", "Compare Changes". Three clicks and you're done. No need to memorize the marker syntax.
Most conflicts come from working on the same file for days without syncing. Habits that prevent them: 1. Pull dev every morning. 2. Keep feature branches short-lived (merge within 1-3 days). 3. Use CODEOWNERS to discourage stepping on each other's domain. 4. When you do have to touch a shared file, mention it: "hey, I'm refactoring portal/index.html today, you might want to wait."
You've both been merging feature branches into dev. dev.site.com has accumulated a week of improvements. You and Josh look at it together (over a call, or over screenshots), and you both feel good. Time to push to prod.
After the merge, tag the moment so you can roll back easily if needed:
git checkout main && git pull && git tag v1.4 -m "card-flip + scoreboard" && git push --tags
Now v1.4 is a permanent bookmark to that exact production state. If something breaks after a future release, you can rebuild from v1.4 instantly.
The dev branch may have unfinished work that's not safe to ship yet. In that case, you branch directly off main: git checkout main && git checkout -b hotfix/auth-bug → fix → PR hotfix → main → fast-track approve → merge. Then merge main back into dev so dev catches up. Rare, but important to know the escape valve exists.
dev branch as shared staging) matches your goals — single staging URL, two-stage approval gatesdev → main. Both approve. Merge. CF Pages redeploys.