From Feature Branch to Production: My Shipping Checklist
The checklist I like before shipping: branch discipline, scoped changes, tests, lint, typecheck, build, smoke testing, and honest release notes.
Shipping is where software becomes real. Until then, code is only an idea in a branch.
I like moving fast, but I do not like random shipping. A small checklist catches many mistakes before users see them.
Start with a clean task
Before coding, I want the task to be clear enough that I can explain the finish line.
Not perfect. Just clear.
Good task:
Add unsubscribe endpoint for blog subscribers.
It should be idempotent for already unsubscribed emails.
Unknown emails should return 404.
Bad task:
Fix subscription.
The first task can be tested. The second one invites guessing.
Use a branch that says what changed
Branch names do not need to be clever.
git switch -c feature/blog-subscription-api
That is enough. When multiple tasks are happening, clear branch names reduce confusion.
Read before editing
This is where many bugs start. A developer opens one file, sees one pattern, and edits too quickly.
I try to read:
- Nearby files
- Existing tests
- Package scripts
- Shared constants
- Previous task context if it exists
Most codebases already have an answer for how to do the next change. You just need to find it.
Write the risky test first
I do not write tests for every character I type. But if the behavior can break users, I want a failing test before the implementation.
For example:
it("keeps unsubscribe idempotent", async () => {
await subscribe("hello@example.com");
await unsubscribe("hello@example.com");
const second = await unsubscribe("hello@example.com");
expect(second.status).toBe("unsubscribed");
});
This test becomes a contract. It tells future me what must not break.
Keep the diff small
A good diff is easy to review. It changes the files needed for the task and leaves unrelated improvements for later.
I watch for these signs:
- Did I rename things not related to the task?
- Did I format files I did not touch?
- Did I move code just because I felt like it?
- Did I add a dependency for something small?
Small diffs ship faster because they create less doubt.
Run the boring commands
Before production, I want the boring checks:
bun run lint
bun run typecheck
bun run test:all
For frontend work, I also want a build. For UI-heavy changes, I want a browser smoke test. If I cannot run something, I say that clearly in the final note.
Smoke test the user path
Tests are not a replacement for using the product once.
For a blog site, I would check:
- Home page renders
- Blog listing renders
- Article route works
- My Reads route works
- Search index returns content
- RSS feed builds
- Mobile layout does not break
For a payments app, the smoke test is much stricter. The checklist should match the risk.
Write honest release notes
I do not like vague release notes.
Bad:
Improved app.
Better:
Added unsubscribe endpoint, validation errors, and idempotent unsubscribe behavior.
Verified with integration tests and lint/typecheck.
The second version helps the next person understand what actually changed.
Final thought
Shipping well is not about moving slowly. It is about removing avoidable risk.
A clear task, focused branch, small diff, tests, build, smoke test, and honest notes are enough for most work. The process is simple because simple processes are easier to repeat.