· 3 min read · Git, DevOps, Workflow
A simple Git workflow for small teams and solo developers
A lightweight Git workflow that keeps main deployable: short-lived branches, pull requests, clear commit messages, rebasing, and preview deploys, without heavy process.
Git can support very elaborate workflows, with release branches, hotfix branches and develop branches. Most small teams don't need any of that. What they need is a main branch that always works, a clear way to get changes into it, and a history that explains why the code looks the way it does.
This is the workflow I use, whether I'm working alone or with a small team.
Rule 1: main is always deployable
Nobody commits directly to main. Anything on main can go live at any moment, and on most projects it does, automatically. That single rule removes most of the fear from deploying.
On GitHub, enforce it with a branch protection rule: require a pull request, and require checks like the build and tests to pass before merging.
Rule 2: one short-lived branch per change
git switch main
git pull
git switch -c fix/contact-form-validation- Name branches by type and topic:
feat/,fix/,chore/,docs/. - Keep them small. A branch that lives for a day or two is easy to review and rarely conflicts. One that lives for a month becomes a merge project.
- If a feature is large, merge it in pieces, hidden behind a flag if necessary.
Rule 3: commit messages explain why
The diff already shows what changed. The message should say why, so that someone reading git log or git blame a year later understands the decision.
# Not helpful
fix stuff
update page.tsx
# Helpful
Reject contact messages with an empty body
Bots were submitting the form with only a name, which reached
the inbox as blank emails.Write the first line as an instruction ("Add", "Fix", "Remove"), keep it under about 70 characters, and add a body when the reason isn't obvious.
Rule 4: stay up to date by rebasing
When main moves on while you work, bring your branch up to date by rebasing onto it. Your commits are replayed on top of the latest code, and the history stays in a straight line.
git fetch origin
git rebase origin/main
# resolve any conflicts, then
git push --force-with-leaseOnly rebase your own branches. --force-with-lease refuses to overwrite the remote if someone else pushed to it in the meantime, which makes it a much safer choice than --force.
Rule 5: every change goes through a pull request
Even working alone, a pull request is worth opening:
- It triggers the build and tests before anything reaches production.
- With hosts like Netlify or Vercel, it creates a preview deploy, a live link for the branch that you or a client can check before it goes live.
- It's a record of what changed and why, linked to the commits.
- Reading your own diff in the PR view catches mistakes you didn't notice in the editor.
Keep descriptions short: what changed, why, and how to check it. Squash-merge small PRs so main gets one clean commit per change.
Things that save the day
git stash # park unfinished work to switch branches
git commit --amend # fix the last commit (before pushing)
git reflog # find a commit you "lost" after a bad reset
git revert <sha> # undo a commit on main safely, with a new commit
git bisect # find which commit introduced a bugOn a shared branch, prefer git revert to rewriting history. It undoes the change without surprising anyone who already pulled.
Keep secrets out of the repository
Add .env files to .gitignore from the first commit. If a secret is ever committed, rotate it: deleting it later leaves it in the history. I've covered this in more detail in environment variables and secrets in Next.js.
Checklist
- Protect
mainand keep it deployable - One small, short-lived branch per change
- Commit messages that explain why
- Rebase onto
main, push with--force-with-lease - A pull request with checks and a preview for every change
- Revert, don't rewrite, on shared branches
Want help setting up a workflow and automatic deploys for your project? Get in touch.