If you're not careful, your build times for Nuxt can spiral out of control. After enabling Nuxt i18n, we reached 27-minute production deploys.
Once we had added a load of nice-to-have Netlify plugins, our deploy times went up again. Before I knew it, our deploys on Netlify were failing, timing out at 30 minutes.
This post addresses build time problems for sites using version 2.14
of Nuxt. 2.14
introduces full-static builds and includes all the lovely new crawler changes.
Note: I will refer to the time it takes for Nuxt to run
generate
as build times. Deploy times are the time it takes Netlify to rungenerate
and then publish it.
For this post, I'll be using the globally recognised "turd" scale for measuring satisfactionโ๐ฉ๐ฉ๐ฉ to ๐๐๐ฅ.
๐ฉ๐ฉ๐ฉ 30-minute deploys (timeout)
Unnecessary Content
Unnecessary content was an issue for us. Having imported nearly 600 articles from our legacy WordPress site, we were building pages for every category, tag, and author too. This lead to some 17500 physical pages being rendered by the Nuxt full static build. After reviewing the metadata on our posts, I managed to reduce our build to just over 3000 pages...
Our Netlify build jumped from 30-minute timeouts to 15 minute deploys.
๐๐ป๐๐ป๐๐ป 15-minute deploys
Skip Optional Dependencies
While not Nuxt specific, only installing the dependencies you need can speed up the Netlify install before the build even starts.
The --no-optional
argument will prevent optional dependencies from being installed by npm
. There is a yarn equivalent.
You can add this to Netlify in the config, or add it to the environment variables on the dashboard.
# netlify.toml
[build.environment]
NPM_FLAGS = "--no-optional"
This did nothing for our deploy time, but it may help others. ๐คช
๐๐ป๐๐ป๐๐ป 15-minute deploys
Code Minification
Nuxt has strong default HTML minification settings used for post-processing builds.
// ...
html: {
minify: {
collapseBooleanAttributes: true,
decodeEntities: true,
minifyCSS: true,
minifyJS: true,
processConditionalComments: true,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
trimCustomFragments: true,
useShortDoctype: true
}
},
// ...
Nuxt already minifies CSS and JS using WebPack plugins. So we can disable inline CSS and JS minification.
// ...
build: {
html: {
minify: {
minifyCSS: false,
minifyJS: false,
}
}
}
// ...
When I read about this, it suggested we'd see a 10x improvement on the initial build. In reality, we saw a minute or two in reductions.
๐๐ป๐๐ป๐๐ป 13-minute deploys
Turn Off Logging
Even locally, the verbose logging of several thousand lines in the terminal can slow EVERYTHING down. Most of the logging is formatted from Nuxt, to.
Disable logging anything but errors with the CI
environment variable.
You can add this to Netlify in the config, or add it to the environment variables on the dashboard.
# netlify.toml
[build.environment]
CI = "1"
This made a surprising difference, slicing a fair chunk off our build time.
๐๐ป๐๐ป๐๐ป 8-minute deploys
Post Processing
If you've played with Netlify deployment configurations, you'll know there is a wealth of options now.
I had lots of options ticked, and we've already established that Nuxt does most of it already.
Reasons to turn them all ofโfor us at least:
- We already have a plan to generate our images based on rules we've established for media creation already. We can do the optimisation steps and CDN uploads here in the future.
- Nuxt does minification of HTML, JS and CSS already.
- The Nuxt static build does-Pre-rendering.
Tick, tick, tick. All off.
๐๐ป๐๐ป๐๐ป 5-minute deploys
Real Talk
Getting our deploys much quicker without paying for enterprise Netlify is unrealistic. I have a ton of optimisation to do on my Vue components, but I don't expect to see much more time saved.
It wasn't actually the production build I was so keen to reduce the time on, it was the previews as we've gone FULL NETLIFY and adopted Netlify CMS for git-based content storage. So, every time we edit a post in Netlify CMS, it creates a pull-request for the edited file. At 30 minute builds, with a team of 8 people working on content, well you see where this is goingโlots of waiting for builds. Less now, at least.
BUT WAIT, There's More ๐ฎ
Is there a way to reduce our preview builds? That was the original motivation to reduce build times in the first place!
After a brief Googlin', I came across issue #6138 raised on the Nuxt project, on how to generate a single route.
In the latest version of Nuxt, the solutions in the GitHub issue didn't actually work.
But, it did give me an idea.
Since Nuxt 2.14
, we've had the crawler to discover pages. And, if I want to provide additional routes, I can use the routes()
property of the generator config.
So, I thought to myself, "can I turn off the crawler and provide it a single route, somehow"?
The answer was yes.
Casually breaking my nuxt.config.js
...
generate: {
crawler: false,
routes() {
return ["/blog/a-test-blog-post-made-in-netlify-cms"]
}
}
Doing this resulted in almost immediate build times, so once tested on Netlify I was down to about 1 minute deploys. It was just building the physical routes (everything in your /pages
directory) without crawling for any dynamic routes. Our physical routes make up less of our site than was worth worrying about.
Could I make this context-driven based on the preview build?
Well, it hit me like a slap around the face.
The slug for the new post created in Netlify CMS was part of the branch name.
cms/blog/a-test-blog-post-made-in-netlify-cms
And, the branch name was available on our preview build as the environment variable, HEAD
.
# console.log(process.env.HEAD)
cms/blog/a-test-blog-post-made-in-netlify-cms
I'm getting closer.
A quick explore of the other environment variables provided in preview builds, I noticed PULL_REQUEST
is an indicator whether the build is from a pull/merge request (true
) or not (false
).
So here was the rough code I put together to make use of this. Add a new function to the top of the nuxt.config.js
file.
// nuxt.config.js
// ...
const isPreviewBuild = () => {
return process.env.PULL_REQUEST && process.env.HEAD.startsWith('cms/')
}
// ...
module.exports = {
// etc...
So this returns whether its a PR and if the branch name begins with cms/
(generated by Netlify CMS).
How can we use this? I'm glad you asked. Edit the "generate" property in nuxt.config.js
.
// nuxt.config.js
// ...
module.exports = {
// ...
generate: {
crawler: !isPreviewBuild(),
routes() {
return isPreviewBuild() ? ["/blog/a-test-blog-post-made-in-netlify-cms"] : []
}
}
}
Tested? Works, still my ~1 minute deploy! Now, route from the branch name. Another new function to nuxt.config.js
.
// nuxt.config.js
// ...
const previewRoute = () => {
const [, type, slug] = process.env.HEAD.split('/')
return [ `/${type}/${slug}` ]
}
// ...
module.exports = {
// etc...
The type
is super important because it means we can also preview video
and author
pages as well blog
. Really cool side effect. Added with one last edit to the nuxt.config.js
file.
// nuxt.config.js
// ...
module.exports = {
// ...
generate: {
crawler: !isPreviewBuild(),
routes() {
return isPreviewBuild() ? previewRoute() : []
}
}
}
๐๐๐ฅ 1-minute deploys
Conclusion
Confession: I built most of the final code without testing it, I used the
BRANCH
environment variable thinking it would be the branch name. It is not. So when it didn't work, I thought I'd gone down a big old rabbit hole building this. I thought I'd wasted hours. Nope, got the environment variable wrong. Read the docs, folks.
It's been almost 5 years since I was in a role that had me building code that would be relied upon in a production environment. I shouldn't be surprised (but I am) that the off-the-shelf configuration for Nuxt isn't optimised for production builds.
I'm generally cautious of introducing environment-aware code into an application, but I have ignored my better judgement as this is environment-aware-configuration as code. You should be very careful about introducing code that fundamentally changes how an application runs or is built based on the environment it is running in.
- Know your platform.
- Read the flipping manual.
- Do all the Googlin'.
- Take care with environment-aware code.
See exactly how we're using this on our GitHub repository.
Nexmo / deved-platform
Nuxt.js for the new Vonage Developer Education site.
Credits
- issue #6138 raised on the Nuxt project ๐
- 10x Faster Nuxt Builds on Netlify โค๏ธ
- Decent help from the Nuxt Discord community ๐ฅ