How to reduce the size of an application based on Nuxt.js?

How to reduce the size of an application based on Nuxt.js?

On a daily basis we try our best to reduce the size of an application on clients’ side in order not to load their Internet connections too much when it’s poor and to improve their overall user experience – what is interesting, an average size of resources which are loaded by Internet websites is increasing year by year. It has also been caught on that the size of resources for a correctly optimized web should be around 1-1.5MB and it should not be larger than 3MB.

How to reduce the size of an application based on Nuxt.js?

https://httparchive.org/reports/page-weight?start=earliest&end=latest#bytesTotal

If we want our front-end to reach such excellent results we need tools which usually occur in the form of dependencies like node_modules (a lot of dependencies).

We have to be aware that when we want to install one dependency – we can get 20 additional ones (dependencies of the installed package) – it means that after the installation process of all the required tools (necessary to transpile, compile, test, lint etc.) our project grows rapidly and might weigh almost 1GB. One might wonder if 1GB is a lot, our local computers have a lot of disc space and it does not seem to be a problem to install all the dependencies from time to time. However, while a single developer’s computer might be ready for it, Continuous Integration environment may not necessarily be ready – these processes instal dependencies much more frequently – additionally, they co-share resources with other projects, which affects disc space, I/O processes efficiency or the condition of the Internet connection itself.

Therefore, we should take care of not only end user but also of our CI environments as such big size of applications makes deployment time longer and as a result developers’ frustration. I know by experience that nothing irritates as much as long time of releasing a small change.

There are a few directions to optimize an application - we as front-end developers can deal with the most painful issues related to size. In this article you will get to know how to reduce the number of node_modules package - maybe to zero?

Let’s get rid of node_modules

The easiest thing we can do is to leave only these dependencies which are required for the application to function in a production mode – to achieve that we differentiate the modules installed between “dependencies” - production dependencies and „devDependencies” which is the dependency for development mode.

To demonstrate the issues in a better way - let’s analyze the Nuxt.js project which has been created by  https://github.com/nuxt/create-nuxt-app with one additional module - @nuxtjs/pwa.

Let’s have a look at package.json file:

 "dependencies": {
    "@nuxtjs/pwa": "^3.3.4",
    "core-js": "^3.8.2",
    "nuxt": "^2.14.12"
  },
  "devDependencies": {
    "@nuxtjs/eslint-config": "^5.0.0",
    "@nuxtjs/eslint-module": "^3.0.2",
    "babel-eslint": "^10.1.0",
    "eslint": "^7.18.0",
    "eslint-plugin-nuxt": "^2.0.0",
    "eslint-plugin-vue": "^7.4.1"
  }

It looks great, doesn’t it? We can see 9 packages, including 3 for the production mode. Now we can look at the sizes of node_modules directory:

➜ nuxt-standalone git:(master) ✗ ls node_modules | wc -l && du -sh node_modules
     809
186M node_modules

We can notice that when we want to kickstart a simple project we need 9 dependencies, but in reality this simple project needs another 800 dependencies which costs us about 200MB. For instance, node_modules which we need in our projects (which are working based on monorepo and yarn workspaces) may have ~800MB.

Step one - dependencies only for production

We already know that we don’t need all dependencies to run applications in a production mode - “devDependencies” are needed only for building process. So that’s why we can build our app and keep only production dependencies.

Yarn build 
yarn install --production 

„Yarn install --production” installs production packages only (“dependencies”) - even if we have development dependencies - this command will remove them.

Let’s have a look how the node_modules directory looks now:

➜ nuxt-standalone git:(master) ✗ ls node_modules | wc -l && du -sh node_modules
     724
148M node_modules

Does it look better now? Unfortunately not - we expected smaller numbers here, but Team of Nuxt.js took care of developers’ mental health and prepared a nice experimental feature for us. Get to know standalone build mode.

Step two - Nuxt build --standalone

We found out about this feature fighting GitHub issues. Everything started from the “clarkdo” comment, which rightly noted one of the potential issues of using “nodeExternals”. Nuxt.js community doesn’t have to wait long for the answer, because the prototype solution was implemented two days later. But what is it about?

https://github.com/nuxt/nuxt.js/pull/4645 

Step two - Nuxt build --standalone

Build standalone lets you bundle node_modules that are normally preserved as externals in the server build - that’s why we should be able to get rid of all node_modules dependencies.

Normal build:
Size of server.js: 22 Kib
Cost of dependencies (vue, vue-router, lodash (/omit), vue-no-ssr, debug): 7.4M
Cold start: 306.385ms
Memory usage: 29.9 MB (RSS: 106 MB)
Standalone build:
Size of server.js: 145 Kib
cold start: 306.550ms
Memory usage: 28.6 MB (RSS: 106 MB)

https://github.com/nuxt/nuxt.js/pull/4661

Unfortunately, you can’t delete all node_modules, because you still need the @nuxt/cli package to  run your app by “yarn start” script. There is a way you can get around this issue:

“npx nuxt-start”

In this way you don’t have to install all node_modules again to run your application. “npx” command will try to find binary file in your local node_modules in the first place - if it doesn’t find it, it will start searching in global node_modules - if it doesn’t find it there, it will call remote npm registry to resolve this package from the Internet.

I'm aware that this solution may not be the best one when it comes to production, because without the definition of a specific version of nuxt-start you will always get the newest one or the one you own locally. Therefore, this process may look different depending on your deployment. If you use a docker container, you can install a specific version of nuxt-start there globally or prepare additional scripts which will remove all node_modules and install only nux-start after your application is built. 

One thing worth mentioning, when you want to build your app in standalone mode is using “buildModules” instead of “modules” during the definition of used modules in nuxt.config.js Using buildModules helps to make production startup faster and also significantly decreases the size of your node_modules for production deployments. You have to remember to refer to the docs for each module to see if it is recommended. You can read more information here.

// Modules for dev and build (recommended) (https://go.nuxtjs.dev/config-modules)
buildModules: [
  // https://go.nuxtjs.dev/eslint
  '@nuxtjs/eslint-module',
  '@nuxtjs/pwa'
],

// Modules (https://go.nuxtjs.dev/config-modules)
modules: [
],

Although the author of the solution on GitHub claims that it is an experimental option and should be tested well first, we are just launching the first project in this mode and we didn’t notice any issues related to this feature. It allowed us to reduce the size of the docker images from 800MB to 65MB, which automatically shortened deployment time and decreased the demand for disc space on integration servers.

Reduced Docker image size from 800MB to 65MB, enhancing deployment efficiency.