How can we create faster Nuxt builds on Netlify without affecting UX or site performance?
So the question is, how can we create faster builds without affecting UX or site performance? TL;DR:
- Optimise HTML minification
- Mute logs
- Remove unneeded dependencies
- Disable “all” post-processing
Let’s first have a look at our Nuxt + Netlify setup:
Generating static Nuxt websites
Nuxt is a framework to build isomorphic Vue apps. Meaning application routing and rendering HTML work both on the server (on first page view) and in the browser (on subsequent navigations). However this setup requires a Node.js server to do the server-side part. We like to keep our setups as simple and carefree as possible. So we prefer static hosting on a CDN (content delivery network) over a dynamic Node.js server. Luckily Nuxt has a command to generate static deployments:
nuxt generate
This command pre-renders each page (and supports dynamic routes too). All you need to know is which pages (routes) to render and when to render them. In our case it’s often a git push, publishing from a cms or a cron job triggering a Netlify deploy which runs nuxt generate
.
Nuxt generate works great on Netlify. But the larger our websites get, the slower our deploys become. And if they take too long Netlify deploys fail with a “Build exceeded maximum allowed runtime” error.
What can we do to fix this?
Optimise HTML minification
Nuxt warns for API calls slowing down generating static pages and advises using the payload option. However even without API calls and without using the store we found large HTML pages significantly slowed down the build.
After a journey through the Nuxt source code we stumbled upon the default HTML minification settings used for post-processing builds:
build: {
html: {
minify: {
collapseBooleanAttributes: true,
decodeEntities: true,
minifyCSS: true,
minifyJS: true,
processConditionalComments: true,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
trimCustomFragments: true,
useShortDoctype: true
}
}
}
While we encourage squeezing out every last byte on the critical rendering path, there are two settings that spoil our build times: minifyCSS
and minifyJS
.
Nuxt already minifies CSS using OptimizeCSSAssetsPlugin and minifies JS using TerserWebpackPlugin. So we can actually disable inline CSS and JS minification for the HTML minifier in our build settings:
build: {
html: {
minify: {
collapseBooleanAttributes: true,
decodeEntities: true,
- minifyCSS: true,
+ minifyCSS: false,
- minifyJS: true,
+ minifyJS: false,
processConditionalComments: true,
removeEmptyAttributes: true,
removeRedundantAttributes: true,
trimCustomFragments: true,
useShortDoctype: true
}
}
}
Running Nuxt generate with these settings results is equal page sizes, but much faster build times. For example, the build time of one of our projects with 833 pages went down from 5h33m21s to 1m38s. That’s over 200x faster!
We also experimented with disabling html.minify
entirely, but this didn’t have any significant effect on the build time.
This benchmark was performed locally by running time nuxt generate
as Netlify times out after 30 minutes. Deploys on Netlify will be slower than on a local machine, but at least now we can build all of our pages on Netlify.
Skip streaming logs
On Netlify we run into our next nuxt generate
issue. For every page Nuxt generates, it logs a line to the console. And on Netlify this causes our deploys to slow down. So we reached out and got a useful reply:
It does look like your build is logging out a lot of lines. Note that because of the way we stream build logs, each logged line does have a time cost. The more you have, the more likely it can slow down your build.
— Dennis from Netlify
As we couldn’t do anything on the Netlify side, we filed an issue on the Nuxt repository, followed by a pull request to remedy the issue. Eventually the issue was solved by adding a quiet option to the generate command. So now you can disable all output except for errors using:
nuxt generate --quiet
Note this option is not in the Nuxt documentation on generate, but can be found when running nuxt generate --help
and is also described as a build option. Also note that npm uses the same --quiet
flag as a loglevel alias, but Nuxt ignores the npm loglevel.
On Netlify we like to run the default generate command as it’s part of a larger build script. Luckily Nuxt also checks for a CI
environment variable. So we can achieve the same using:
CI=1 nuxt generate
Now we can keep our build script, and configure the CI
environment variable on Netlify via the dashboard or in our netlify.toml
file:
# netlify.toml
[build.environment]
CI = "1"
With this simple change we saw our build times drop immediately.
Skip optional dependencies
While not Nuxt specific, there’s another simple change we can apply to make our Netlify deploys faster: only install the dependencies you need and skip optional dependencies.
We sometimes depend on large packages like Cypress. Cypress is a great testing framework. But is dramatic for our npm install times. So we can make it an optional dependency:
"optionalDependencies": {
"cypress": "3.2.0"
}
Now our other CI services can still use Cypress, but we can drop our optional dependencies on Netlify using the npm --no-optional
flag:
The
--no-optional
argument will prevent optional dependencies from being installed.
Again we configure this using an environment variable on Netlify. In this case with the NPM_FLAGS
variable:
# netlify.toml
[build.environment]
NPM_FLAGS = "--no-optional"
Note that the same is possible for Yarn (yarn install --ignore-optional
). But shouldn’t be necessary on Netlify when you have a yarn.lock
in your repository.
Skipping our optional dependencies typically saves us a few minutes per build.
Disable post processing on Netlify
Nuxt already does all our post processing optimisations. So we don't need Netlify to do this. We can disable most post processing in our Netlify settings > Build & deploy > Post processing:
Obviously we don’t need Netlify Pre-rendering as we use Nuxt generate to do just that. Nuxt also does all of our asset optimisation and could do snippet injection when needed. But even with all these settings disabled, Netlify’s post processing still takes ~ 2 minutes:
So we reached out to the Netlify community and received some useful feedback:
Two things you can’t control in post processing:
form detection. We can disable this for you as long as you have optimisation totally disabled (I see you do), AND you don’t want to use our forms service. If this is true, let me know your site’s API ID and I can turn off forms processing for you.
mixed content warnings. We look for
http://
links in your pages and tell you about those so you’ll be aware that a browser is likely to act ugly when displaying the pages. No way to disable this.
In the project above, we are not using Netlify forms, so we requested to turn off form-processing:
With the form-processing disabled our post processing times are down from ~2min to ~5sec!
Phil Hawksworth liked the idea of making this a configurable feature, so maybe we’ll be able to manage this through the Netlify dashboard in the future.
Fast deploys FTW
With these optimisations we are able to generate and deploy hundreds and sometimes even thousands of Nuxt pages on Netlify in a matter of minutes.
We’ll keep experimenting with new ways to optimise the speed of our builds. Tweaking generate.concurrency
had inconsistent results and using nuxt-generate-cluster
produced incorrect builds. Maybe incremental Nuxt builds, content sharding and Netlify’s secret cache (/opt/build/cache/
) can make our future builds even faster.
Do you want to become a Vue master? During our two day hands-on workshop we’ll teach you everything you need to know to build large performant web apps with Vue. Vue is the SPA framework powering Nuxt, so a better understanding of Vue enables you to build better Nuxt projects.
A special thanks to our colleague Frank for figuring out the log issues and reviewing the other optimisations in this post. And a big thanks to the Nuxt and Netlify teams for their amazing products and fast & friendly feedback on all our questions!