Why a well-written changelog is gold
The changelog is the first thing a dev looks at when something broke after an update. If your changelog says "various bugfixes and improvements", the dev has to read every commit to understand what changed. If it says "Fixed: race condition in user creation with same email", the dev knows in 2 seconds whether your fix affects their case. That time difference, multiplied by thousands of users, is real value.
The Keep a Changelog standard (keepachangelog.com) defines six categories: Added, Changed, Deprecated, Removed, Fixed, Security. Maintaining that nomenclature allows automatic parsing: GitHub Releases, Dependabot and upgrade tools can extract breaking changes and vulnerabilities without manual marking.
Combine Keep a Changelog with Semantic Versioning: MAJOR.MINOR.PATCH versions change based on entry type. If you add a Removed or Changed that breaks API, MAJOR bumps. If only Added backward-compatible, MINOR bumps. If only Fixed, PATCH bumps. That relationship between changelog and version makes automations like semantic-release calculate versions from commits without human intervention.
How to write changelog entries that matter
Rule #1: always from user perspective, not dev perspective. "Refactored auth module" tells the user nothing. "Login now supports Google and Microsoft" does. The user doesn't need to know how you did it, only what changed in their experience. If the change is purely internal with no observable impact, it probably doesn't deserve an entry in public changelog.
Rule #2: include enough context for an external dev to understand impact. "Fixed: bug in checkout" is useless. "Fixed: cart loss when changing currency during checkout in Safari iOS" lets an external dev verify if their system had that specific bug. Mention browser, OS, version, specific conditions when relevant.
Rule #3: link to issues, PRs or commits when it adds value. Vue.js, React and VS Code link each entry to its PR/issue. That allows seeing exact code of the change. For internal (company) changelog, you can link to Jira or Linear. For public open source, GitHub references. Traceability builds trust in releases.
Typical mistakes in real changelogs
Mistake #1: changelog auto-generated from commits without curation. Tools like conventional-changelog generate from commits, but if your commits are "fix typo", "wip", "ggg", the changelog ends up trash. Auto-generation only works if your team already writes descriptive commits following Conventional Commits.
Mistake #2: mixing versions in single entry. "Version 2.0-2.5: many changes..." helps no one. Each release should have its own clear section with date and version. If your changelog has 6 months without update, you lost the habit and changes are no longer traceable. A changelog requires per-release discipline, not batch at the end.
Mistake #3: treating internal changes as public. "Updated internal logger" doesn't matter to external user unless behavior changes. If your library has public and private API, separate public changelog (what affects integrators) from internal (refactors, internal deps). Stripe, Twilio and Atlassian have this split: public vs internal.
Automation: changelog from commits
If your team follows Conventional Commits (feat:, fix:, docs:, etc.), you can automate changelog with semantic-release, release-please (Google) or changesets (popular in monorepos). These tools read commits since last tag, categorize them, calculate next version and generate Keep a Changelog format automatically.
Typical flow: dev makes PR with commit "feat(auth): add Google OAuth", merge to main triggers CI, semantic-release detects feat: and bumps MINOR, generates entry in CHANGELOG.md, creates git tag and GitHub release. All without manual intervention. Next.js, Prisma and Vercel SDK use this flow in production.
For projects where human curation matters (consumer products, not libraries), changesets lets each PR manually add changelog entry in separate .md file. On release, entries merge. This gives control over language (user perspective, not technical commit) and prevents minor changes from cluttering public changelog. shadcn/ui and tRPC use changesets successfully.