Blog

10x Faster Nuxt builds on Netlify

How can we create faster Nuxt builds on Netlify without affecting UX or site performance?

We love building fast, progressively enhanced websites for everyone using Nuxt and Netlify. But building hundreds of web pages with Nuxt on Netlify resulted in builds taking up to 30 minutes or more, causing a timeout on Netlify and our sites failing to deploy :(

Netlify deploy failing after 30 minutes
Netlify deploy failing after 30 minutes

So the question is, how can we create faster builds without affecting UX or site performance? TL;DR:

  1. Optimise HTML minification
  2. Mute logs
  3. Remove unneeded dependencies
  4. 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
Nuxt generate log: generated 833 pages in 1m38s
Nuxt generate log: generated 833 pages in 1m38s

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.

npm documentation

Again we configure this using an environment variable on Netlify. In this case with the NPM_FLAGSvariable:

# 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:

post processing options for our Netlify project
post processing options for our Netlify project

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:

Netlify deploy logs show post processing takes ~2min with default settings
Before: Netlify deploy logs show post processing takes ~2min with default settings

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.

Chris from Netlify

In the project above, we are not using Netlify forms, so we requested to turn off form-processing:

Netlify deploy logs post processing takes ~5sec with form-processing disabled
After: Netlify deploy logs post processing takes ~5sec with form-processing disabled

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.

Join our Vue masterclass

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!

← All blog posts

Also in love with the web?

For us, that’s about technology and user experience. Fast, available for all, enjoyable to use. And fun to build. This is how our team bands together, adhering to the same values, to make sure we achieve a solid result for clients both large and small. Does that fit you?

Join our team