Performance budget for Next.js
Matias Daniel Torre
Front End Engineer at PedidosYa | Javascript | TypeScript | React.js | Node.js | Next.js
How to avoid the performance decrease in your application
When you start to improve the performance in any application, is difficult to validate this values, because new features is adding constantly. If you don't have a validation to check the current performance very often, the value is decrease, similar when you improve the accessibility, one month later the accessibility decrease.
How to validate the performance constantly
The first step is create a performance budget, is a way to prevent the application decrease the performance, it can apply to javascript, images, css, metrics of core web vitals (LCP, FID, CLS)
Libraries
Libraries for this performance budget
- bundlesize Check the current size of your bundle
- @next/bundle-analyzer Analyze the current bundle
- @lhci/cli CI for lighthouse to apply a budget for core web vitals
Adding the performance budget
- Create an example app using npx create-next-app basic
- Install npm i bundlesize @next/bundle-analyzer @lhci/cli --D
If you have a current application skip the step 1)
Bundle Analyzer
The first to configure is the bundle-analyzer , you need to apply the current plugin in your next.config.js file
const { ANALYZE } = process.env const withBundleAnalyzer = require("@next/bundle-analyzer")({ enabled: ANALYZE === "true", }); module.exports = withBundleAnalyzer({});
This allows to apply the current plugin if the environment variable ANALYZE is true
In your package.json add the new command to execute
"analyze": "ANALYZE=true next build"
To check if is correct run the command npm run analyze
This plugin allows to check the current dependencies for both parts (client | server) the most important is client-side because is the code shipping to the browser, validate if you have a huge dependency.
For easy validation I recommend using https://bundlephobia.com/ to get the current size of any library, and validate if the current library has three-shaking support
Bundle Size
The second is configure the bundlesize it allows to validate the current bundle
Create a file bundlesize.config.json in the root of the project to define the max size of the current bundles / chunks
Basic configuration
- path: The current path where is the file
- maxSize: The maximum size to avoid the bundle increase
- compression: The current compression brotli o gzip for the exact size for production
{ "files": [ { "path": ".next/static/**/main-*.js", "maxSize": "30 kB", "compression": "brotli" }, { "path": ".next/static/chunks/pages/my-page-*.js", "maxSize": "8 kB", "compression": "brotli" }, { "path": ".next/static/chunks/pages/_app-*.js", "maxSize": "65 kB", "compression": "brotli" } ] }
Change the path for your pages, using the * it allows to apply in dynamic content with hash
In your package.json add the new command to execute
"bundle-check": "next build && bundlesize"
To check if is correct run the command npm run bundle-check
Lighthouse
The last is configure lighthouse, is a tool to improve the performance in your web apps, it has an option to apply a custom budget to check metrics like performance, accessibility, best practices, seo
Create a file config file called lighthouserc.js at the root of your repo controls the options for Lighthouse CI.
This config is oriented for web-apps the results is uploaded temporal in a public storage
// Budget to check the mininum in each step // 80% minimum performance const PERFORMANCE = 0.8; const ACCESSIBILITY = 0.8; const BEST_PRACTICES = 0.8; const SEO = 0.8; // Extra headers const headers = {}; // URLs to check const URL = process.env.URL; module.exports = { ci: { collect: { url: [URL], }, settings: { maxWaitForFcp: 15 * 1000, maxWaitForLoad: 35 * 1000, skipAudits: ["uses-http2"], options: { networkQuietThresholdMs: 10 * 1000, }, onlyCategories: ["performance", "accessibility", "best-practices", "seo"], chromeFlags: "--no-sandbox", extraHeaders: JSON.stringify(headers), }, assert: { assertions: { "categories:performance": [ "error", { minScore: PERFORMANCE, aggregationMethod: "median-run" }, ], "categories:accessibility": [ "error", { minScore: ACCESSIBILITY, aggregationMethod: "pessimistic", }, ], "categories:best-practices": [ "error", { minScore: BEST_PRACTICES, aggregationMethod: "pessimistic", }, ], "categories:seo": [ "error", { minScore: SEO, aggregationMethod: "pessimistic" }, ], }, }, upload: { target: "temporary-public-storage", }, }, };
In your package.json add the new command to execute
"lighthouse":"URL='https://localhost:3000/my-page' lhci autorun"
To check if is correct run the server and the command npm run lighthouse
The current example is hosted in this repository
Final
The last step is integrate this commands in your CI pipeline when you create a pull request validate this steps, depends if your using github actions o CI internal in your company