iTranslated by AI
Automating Website Versioning with changesets
Introduction
I recently introduced changesets for website version management, and it was incredibly convenient. However, the documentation was a bit hard to follow, so I'd like to write about how to use it and some points where I got stuck.
changesets also includes features for publishing to npm and is used for version control by prominent packages like Chakra UI, Astro, and Emotion. By the way, we also use it in Yamada UI. This time, I want to focus on managing versions for things like websites rather than focusing on the npm publishing feature.
There is also a tool called semantic-release that is often compared. That one seems to bump the version with every commit. Since I often commit somewhat casually, I prefer using changesets, which allows for more flexible version management.
What is changesets?
changesets is a tool that can streamline version control based on semantic versioning. It's very convenient because you can record change logs at your preferred timing, and if you set up GitHub Actions, it will automatically handle things like creating release notes.
Broadly, it has the following features:
-
Change log management
- You can choose the level of change (Major, Minor, Patch) each time.
- It also handles linking with commits and PRs.
-
Automatic creation of release notes
- Creation via command is also possible.
- There is a GitHub Action that automatically creates release PRs.
- It also handles linking PRs, commits, and users.
The release notes look something like this.
It creates PRs like this.

Preparation
The official documentation can be found here.
intro-to-using-changesets.md
Installation and Initialization
The CLI tool is in @changesets/cli, so we'll install that.
While the official documentation suggests installing it directly, I think it's better to add it to devDependencies.
npm install --save-dev @changesets/cli
After installing the CLI tool, first initialize it.
This will create a .changeset folder in the root directory.
npx changeset init
package.json Setup
versioning-apps.md
To manage versions with changesets, at minimum, the following three items must be included in package.json. Conversely, as long as you create a package.json that includes these three items, it seems any project can be version-managed.
{
"name": "package",
"version": "1.0.0",
"private": true
}
- name: Specifies the package name.
- version: changesets bumps the version based on the
versionvalue. If you want to start from0.0.0, changing this will let you bump the version from there. - private: If
privateisfalse, scripts for publishing tonpm, etc., will run. This time we won't be publishing, so set it totrue.
config.json Setup
Running npx changeset init creates .changeset/config.json.
If you are not publishing your program to npm, etc., you need to add the following settings.
{
...
"privatePackages": {
"version": true,
"tag": true
}
}
Usage
You can use changesets commands to generate files that record changes. By including these files in your commits, you can manage your versioning along with the commits and their associated PRs.
Based on the recorded changes, you can output release notes to a CHANGELOG.md file.
Recording Changes
To record changes, run the following command:
npx changeset
When executed, you will be asked what level of version change you have made, as follows:
🦋 What kind of change is this for changeset-zenn? (current version is 0.1.0) …
❯ patch
minor
major
Once selected, you will be asked for a summary of the change, so enter it.
🦋 What kind of change is this for changeset-zenn? (current version is 0.1.0) · patch
🦋 Please enter a summary for this change (this will be in the changelogs).
🦋 (submit empty line to open external editor)
🦋 Summary ›
After entering the summary and pressing Enter, you'll be asked if this is correct. Press Enter again, and the generation of the file recording the changes will be complete.
🦋 === Summary of changesets ===
🦋 patch: changeset-zenn
🦋
🦋 Is this your desired changeset? (Y/n) › true
Upon completion, a markdown file containing the change record will be created under the .changeset folder. A new file is added each time you record a change.
Since it is a markdown file, you can directly modify the version level or update the content if you wish to change the summary.
Include this file in your commit to complete the recording of the changes.

Creating CHANGELOG.md
At the timing of a release, a CHANGELOG.md file is generated based on the accumulated change records.
Run the following command:
npx changeset version
When executed, the markdown files previously stored under .changeset are deleted, and the CHANGELOG.md file is updated. If you have made one minor change and two patch changes, it will look like the following. The version has changed from 0.1.0 to 0.2.0. The version field in package.json is also updated at the same time.
# changeset-zenn
## 0.2.0
### Minor Changes
- 8ccbd1d: Test changelog3.
### Patch Changes
- 0903189: Test changelog2.
- 52036f3: Test changelog.
GitHub Action
changesets also provides a GitHub Action, which is incredibly useful; if you use changesets, you should definitely use this as well.
It handles everything from running npx changeset version and committing those changes to automatically creating a PR. If additional changes are pushed, it incorporates them and updates the existing PR.
Setup Method
With the following workflow, every time a push is made to the main branch, it checks for markdown files recording changes and, if found, reflects them in a PR.
name: Release
on:
push:
branches:
- main
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
release:
name: Release
permissions:
pull-requests: write
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Setup Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Dependencies
run: npm install
- name: Create Release Pull Request
uses: changesets/action@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
It will create a PR like this.
When releasing, you only need to merge this PR, eliminating the need to run npx changeset version manually.
Linking PRs
While the PR created earlier shows which commit the changes are linked to, it does not include information about which PR they originated from or who made the changes.

To change the text in the release notes, you can customize it using the changelog setting in .changeset/config.json. (For details, see Customizing Release Notes.)
Since a tool for linking PRs is already provided, we will use that.
Install @changesets/changelog-github.
npm install --save-dev @changesets/changelog-github
Modify .changeset/config.json to configure the use of @changesets/changelog-github for generating release notes. Change the changelog entry as shown below. In the repo field of the second item, enter your project's remote repository.
{
...
- "changelog": "@changesets/cli/changelog",
+ "changelog": [
+ "@changesets/changelog-github",
+ { "repo": "108yen/changeset-zenn" }
+ ],
...
}
With this setup complete, using it in the same way will now result in automatically created PRs that include links to the PRs containing the changes and the individuals who made them.

Executing other processes (deployment, etc.) during release
You might want to run a deployment process when merging the PR created by changesets.
This can be done by setting up a separate workflow as follows.
This workflow is triggered when a PR targeting main is closed. Additionally, a conditional statement is set to execute the deployment job only if the PR has been merged and it comes from a branch starting with changeset-release.
changesets creates a branch named changeset-release/[target-branch] and creates a release PR from there, so this allows you to execute the process only when the release PR is merged.
name: Deploy
on:
pull_request_target:
types:
- closed
branches:
- main
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
deploy:
name: Deploy
# Run if a PR from changeset-release/** is merged
if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'changeset-release')
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Deploy
run: npm run deploy # Deployment process, etc.
There is also a way to use the functionality of changesets/action@v1 to handle both creating the release PR and deployment in a single workflow. However, please note that this assumes that "the change log is always recorded when pushing to the default branch."
Pattern using the release workflow
If you pass a script to the publish property of changesets/action@v1, it will be executed "when there are no change logs." Therefore, if "no change logs remaining" is equivalent to "the need to run the deployment process," this method works well. You must have an established operation where change logs are always left when pushing to the default branch.
Conversely, if that's not the case, you should separate the workflows. For example, after outputting change logs to CHANGELOG.md, the state becomes "no change logs remaining." If you push to the default branch in this state without creating a new change log, the script passed to publish will be executed.
name: Release
on:
push:
branches:
- main
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
release:
name: Release
permissions:
pull-requests: write
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Setup Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Dependencies
run: npm install
- name: Create Release Pull Request
uses: changesets/action@v1
with:
publish: npm run deploy # Deployment process, etc.
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
When there is a separate branch for release
In cases like Vercel, deployment might occur when pushing to a specific branch. It is possible to change the target branch for PRs issued by changesets. It is recommended to create a release branch and use it as the branch for deployment.
Set the target branch in the branch parameter of changesets/action@v1.
With this, a PR targeting the release branch will be created from a branch checked out from the default branch (main).
name: Release
on:
push:
branches:
- main
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
release:
name: Release
permissions:
pull-requests: write
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout Repo
uses: actions/checkout@v4
- name: Setup Node.js 20
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install Dependencies
run: npm install
- name: Create Release Pull Request
uses: changesets/action@v1
with:
branch: release # Add this
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
It will look something like this.
Supplement
Customizing the Release Notes
modifying-changelog-format.md
As mentioned in Linking PRs, customization is possible via the changelog setting in .changeset/config.json. Create a changelog-function.js file under the .changeset folder and load it in the changelog setting.
...
- "changelog": "@changesets/cli/changelog",
+ "changelog": "./changelog-function.js",
...
The output of getReleaseLine becomes a single line in the release notes. getDependencyReleaseLine is used to describe dependencies in cases like monorepo configurations. (You don't need to worry about this much when using it for web.)
Visuals
The string returned as the value of getReleaseLine corresponds to the single line surrounded by the red box below.

The return value of getDependencyReleaseLine corresponds to the part surrounded by the red box below.

Arguments for getReleaseLine
The arguments are as follows:
type getReleaseLine(
changeset: {
// This is the string of the summary from the changeset markdown file
summary: string
// This is an array of information about what is going to be released. each is an object with name: the name of the package, and type, which is "major", "minor", or "patch"
releases
// the hash for the commit that introduced the changeset
commit
},
// the type of the change this changeset refers to, as "major", "minor", or "patch"
type
// This needs to be explained - see @changesets/changelog-github's code for how this works
changelogOpts
) => string
async function getReleaseLine(changesets, type, options) {
return "changelog"
}
async function getDependencyReleaseLine(changesets, dependenciesUpdated,options) {
return
}
const changelogFunctions = {
getReleaseLine,
getDependencyReleaseLine
}
module.exports = changelogFunctions
@changesets/changelog-github uses the same structure.
Passing Values
To set parameters to be received in options, do the following. This allows 108yen/changeset-zenn to be retrieved from options.repo.
{
...
- "changelog": "@changesets/cli/changelog",
+ "changelog": [
+ "@changesets/changelog-github",
+ { "repo": "108yen/changeset-zenn" }
+ ],
...
}
Creating a GitHub release
It's the one displayed on the right side of GitHub.
It's not strictly necessary since I'm not publishing it as a package, but I set it up because I wanted to create them for myself. In changesets, the GitHub release creation feature is tied to the npm publishing feature and couldn't be run on its own, so I created a dedicated GitHub Action for it. Please feel free to use it if you'd like.
name: Deploy
on:
pull_request_target:
types:
- closed
branches:
- release
concurrency: ${{ github.workflow }}-${{ github.ref }}
jobs:
deploy:
name: Deploy
permissions:
contents: write
if: github.event.pull_request.merged == true && startsWith(github.head_ref, 'changeset-release')
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Create GitHub Release
uses: 108yen/changeset-release@v1
with:
target: release # Set the target branch if you are creating a release for something other than the default branch.
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy
run: [Deployment process, etc.]
If you want to manage it yourself, feel free to copy and paste the code below.
What it does isn't that complicated: it reads the version from package.json and the content of the corresponding version from CHANGELOG.md, then creates a GitHub release based on that info.
Displaying the version on a web page
changesets also updates the version in package.json, so you can simply extract this value. Using find-packages is convenient for this.
npm install find-packages
In the case of Next.js App Router, you can display it as follows:
import { findPackages } from "find-packages";
export default function Home() {
const packages = await findPackages("./");
const {version} = packages[0].manifest;
return <p>{version}</p>
}
Issuing a tag
Executing the following command creates a git tag. It does not perform a push, so if you want to issue a tag on GitHub Actions, please push it using git push --follow-tags.
npx changeset tag
Conclusion
It has been a long post, but once you get the hang of it, you can automate most of your version management, so please give it a try. There are other features I couldn't introduce, such as monorepo management and GitHub Action functionalities, so please look into them if you're interested!
Discussion