Git Workflow for Solo Developers
Most Git advice is written for teams. Here's the workflow I actually use when I'm the only person committing — branches, commits, releases — without the team-of-twelve ceremony.
When you read about Git workflows online, almost everything assumes you have teammates. GitFlow, trunk-based development, code review etiquette, semantic commits enforced by a bot — all of it is built around a team negotiating how to share a codebase.
But a lot of us spend most of our time in repos where the team is one person. Side projects, freelance work, small client builds, experiments. The team-of-twelve workflow doesn't fit, but neither does committing straight to main with messages like "wip" and "fix again". You still want a workflow — you just want one tuned for the actual constraint, which is your own future self three weeks from now trying to figure out what you did.
This is the workflow I use across my solo repos. It's boring on purpose. The point is to leave a clean trail and make rollbacks cheap, not to optimize for team coordination that doesn't exist.
Branching: short-lived, single-purpose
I work on main. That's the production branch. Everything that gets deployed comes from there.
For any change that's bigger than a one-line typo, I branch:
git checkout -b feat/blog-mdx
Naming convention is just three prefixes:
feat/for new featuresfix/for bug fixeschore/for refactors, deps, tooling
That's it. No tickets, no JIRA IDs, no team initials. The branch name's only job is to remind me what I was doing when I context-switch back after lunch.
Branches stay short. If a branch lives longer than a few days, it usually means I bit off too much and should have shipped a smaller version of the change. Long-lived branches drift, and merging them turns into archaeology.
Commits: one logical change per commit
This is where solo workflow tempts you to be sloppy. Nobody's reviewing the diff, so why bother shaping commits?
Because the person reviewing the diff is you in three weeks, when production breaks and you need to bisect or revert. A clean commit history is a debugging tool. A messy one is a punishment.
The rule I follow: one commit = one logical change, with a message that completes the sentence "this commit will ___".
Good:
feat(blog): add MDX rendering with frontmatter and reading time
fix(api): handle empty filter array in /projects query
chore: bump next to 14.2 and patch breaking imports
Bad:
wip
fix
update stuff
asdf
The format is loose Conventional Commits. I use it because it makes git log --oneline actually scannable, not because some bot enforces it.
If I'm hacking and end up with three logical changes mixed together, I use git add -p to stage them separately and commit each one with its own message. Takes an extra two minutes. Saves an hour later.
Pull requests: yes, even on solo repos
I open PRs against main even when I'm the only one merging. Three reasons:
- CI runs on the PR. Vercel preview deploy, lint, tests, type-check. If something's broken, I see it before main breaks.
- The diff view is a forcing function. Looking at your own changes in PR view catches things terminal
git diffskims past — accidental console logs, commented-out code, files you didn't mean to touch. - The PR description is a built-in changelog. When I come back six months later, the PR is documentation of why a change happened, not just what.
I write the PR like I'm reviewing it for a stranger. Summary at the top, what changed, what I verified. Then I merge it myself, usually with squash so main history stays one-commit-per-feature.
When to skip the branch
Solo workflow has to be ergonomic or you'll skip it. I commit straight to main when:
- Fixing a typo in a comment or doc
- Bumping a single dependency that doesn't change behavior
- Updating a config value (env var, model name) that I just need to test on prod
The rule is: if the change is reversible with a one-line revert and isn't worth a PR description, it goes straight to main. Anything more, branch.
Tags and releases: the cheapest changelog
For projects that ship to production, I tag releases:
git tag -a v0.4.0 -m "Add /blogs MDX rendering, refactor projects filter"
git push --tags
This costs about ten seconds per release and gives you:
- A clean rollback point —
git checkout v0.3.0if v0.4 turns out to be a disaster - A scrollable history of what shipped when
- Something to point at when the client asks "what's new since last month"
I don't follow strict SemVer for solo work. If I shipped something user-visible, the minor version bumps. If I had to break a URL or a stored format, the major bumps. Patch is for shipped bug fixes. That's enough structure.
Rebasing: yes, but carefully
I rebase my own branches before merging if the commit history got messy:
git rebase -i main
Squash the typos, reword the bad messages, drop the experimental commits I rolled back. The branch lands clean.
I don't rebase anything that's been pushed and shared, even on solo repos — the moment a CI deploy or a Vercel preview has built from a commit SHA, that SHA needs to keep meaning the same thing. Rewriting history after that point breaks links to deploys, screenshots in PRs, and search results in your own logs.
What I don't do
A few things the team-workflow advice insists on, that I skip on solo repos:
- No develop branch. It's an extra merge with no review benefit when the team is one.
mainis develop. - No mandatory issue linking. I track work in my todo app, not GitHub issues, for solo projects. Issues are for things I want strangers to be able to file.
- No release branches. Tags do the same job for free.
- No commit signing enforcement. Helpful for trust on open source, overkill on private side projects.
The pattern that holds
The pieces above all reduce to one principle: make every change reviewable, revertible, and labeled, even when nobody else is reviewing it. That's the whole workflow.
Future-you is the most important reviewer you'll ever have. Treat their experience well.
