Measuring Bundle Sizes with Next.js and GitHub Actions, Part 2
May 15th, 2021I wrote Part 1 of this post back in February, demonstrating how to measure Next.js bundle sizes with GitHub Actions. Today, I’ll walk through how to show a bundle size diff against master:

We’ll start from the final code from part 1, which comments your project’s bundle sizes on every PR action and push to master. The first thing we need to do is grab something to compare against: we’ll use whatever is currently on master.
Since we’re running our Action on every push to master, we don’t need to recalculate master’s bundle size—we can just use the previously calculated sizes! For this, we can use the Action action-download-artifact:
- name: Download master JSON
uses: dawidd6/action-download-artifact@v2
if: success() && github.event.number
with:
workflow: bundle-size.yml
branch: master
path: .next/analyze/master
This downloads all artifacts from the most recent run of our Action workflow on master into the folder .next/analyze/master. Note that Part 1’s Build & analyze step creates the folder .next/analyze/master—this is necessary for this step to succeed.
Now, we can write a script that compares the bundle.json files across master and our new PR:
const currentBundle = require("../.next/analyze/bundle.json");
const masterBundle = require("../.next/analyze/master/bundle/bundle.json");
const sizes = currentBundle
.map(({ path, size }) => {
const masterSize = masterBundle.find(x => x.path === path);
// if a file exists in our bundle but not master's, it was added
const diff = masterSize ? size - masterSize.size : "added";
})
// if a file exists in master's bundle but not ours, it was removed
.concat(
masterBundle
.filter(({ path }) => !currentBundle.find(x => x.path === path))
.map(({ path }) => "removed")
);
Then, using the same code from part 1, we can output a Markdown table of this diff to publish to a GitHub comment:
const fs = require("fs");
const path = require("path");
const currentBundle = require("../.next/analyze/bundle.json");
const masterBundle = require("../.next/analyze/master/bundle/bundle.json");
const prefix = ".next";
const outdir = path.join(process.cwd(), prefix, "analyze");
const outfile = path.join(outdir, "bundle-comparison.txt");
function formatBytes(bytes, signed = false) {
const sign = signed ? (bytes < 0 ? "-" : "+") : "";
if (bytes === 0) return `${sign}0B`;
const k = 1024;
const dm = 2;
const sizes = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
const i = Math.floor(Math.log(Math.abs(bytes)) / Math.log(k));
return `${sign}${parseFloat(Math.abs(bytes / Math.pow(k, i)).toFixed(dm))}${
sizes[i]
}`;
}
const sizes = currentBundle
.map(({ path, size }) => {
const masterSize = masterBundle.find(x => x.path === path);
const diffStr = masterSize
? formatBytes(size - masterSize.size, true)
: "added";
return `| \`${path}\` | ${formatBytes(size)} (${diffStr}) |`;
})
.concat(
masterBundle
.filter(({ path }) => !currentBundle.find(x => x.path === path))
.map(({ path }) => `| \`${path}\` | removed |`)
)
.join("\n");
const output = `# Bundle Size
| Route | Size (gzipped) |
| --- | --- |
${sizes}
<!-- GH BOT -->`;
try {
fs.mkdirSync(outdir);
} catch (e) {
// may already exist
}
fs.writeFileSync(outfile, output);
The last step is to include this script in our Actions workflow:
# Place this after "Download master JSON"
- name: Compare bundle size
if: success() && github.event.number
run: ls -laR .next/analyze/master && node scripts/compare-bundles.js
# Modify this step to use `bundle-comparison.txt`, the new file we're uploading
- name: Get comment body
id: get-comment-body
if: success() && github.event.number
run: |
body=$(cat .next/analyze/bundle-comparison.txt)
body="${body//'%'/'%25'}"
body="${body//$'\n'/'%0A'}"
body="${body//$'\r'/'%0D'}"
echo ::set-output name=body::$body
If all goes well, you’ll see a bundle size diff in your PR!

