title: Nuxt huge memory usage / leakage and how to prevent
body: <p>I'm on Nuxt v2.13 and Vuetify v2 , also using keep-alive in my default layout. As my app got bigger and bigger , I noticed the memory problem more and more so that my app needs at least around 4GB RAM on cloud server to be built and work properly. I dug around and found scattered pieces, so decided to share them and discuss the solutions.</p>
  <p><strong>Please answer each one according to their #numbers</strong></p>
  <p><strong>#1 - NuxtLink (vue-router) memory leakage :</strong>
  others found that there may be a leakage in vue-router ; also because the DOM associated with the nuxt-link will be prefetched, there also may be a high usage in memory. So someone suggested to use html anchor instead of nuxt-link like this:</p>
  <pre class="lang-js prettyprint-override"><code>&lt;template&gt;
    &lt;a href=&quot;/mypage&quot; @click.prevent=&quot;goTo('mypage')&quot;&gt;my page link&lt;/a&gt;
  &lt;/template&gt;
  
  &lt;script&gt;
  export default{
    methods:{
      goTo(link){
        this.$router.push(link)
      }
    }
  }
  &lt;/script&gt;
  </code></pre>
  <p>what do you think about this approach ?? and what about Vuetify <code>to</code> props as they work like nuxt-link?</p>
  <pre class="lang-js prettyprint-override"><code>&lt;template&gt;
    &lt;v-card to=&quot;/mypage&quot; &gt;&lt;/v-card&gt;
  &lt;/template&gt;
  </code></pre>
  <p><strong>#2 - Dynamic component load :</strong>
  As my app is bidirectional and customizable by <code>.env</code> file , i had to lazy load many of my components dynamically and conditionally like this:</p>
  <pre class="lang-js prettyprint-override"><code>&lt;template&gt;
    &lt;component :is=&quot;mycomp&quot; /&gt;
  &lt;/template&gt;
  
  &lt;script&gt;
  export default{
    computed:{
      mycomp(){
        return import()=&gt;(`@/components/${process.env.SITE_DIR}/mycomp.vue`)
      }
    }
  }
  &lt;/script&gt;
  </code></pre>
  <p>will this cause high memory usage/leakage ??</p>
  <p><strong># 3 - Nuxt Event Bus :</strong>
  beside normal <code>this.$emit()</code> in my components, sometimes I had to use <code>$nuxt.$emit()</code> . i remove them all in <code>beforeDestroy</code> hook :</p>
  <pre class="lang-js prettyprint-override"><code>&lt;script&gt;
  export default{
    created:{
      this.$nuxt.$on('myevent', ()=&gt;{
        // do something
      }
    },
    beforeDestroy(){
      this.$nuxt.$off('myevent')
    }
  }
  &lt;/script&gt;
  </code></pre>
  <p>but someone told me that listeners on <code>created</code> hook will be SSR and won't be removed in CSR <code>beforeDestroy</code> hook. so what should i do? add <code>if(process.client){}</code> to <code>created</code> ??</p>
  <p><strong># 4 - Global Plugins :</strong>
  I found <a href="https://github.com/nuxt/nuxt.js/issues/1984#issuecomment-340412232" rel="nofollow noreferrer">this issue</a> and also <a href="https://nuxtjs.org/docs/2.x/directory-structure/plugins/#inject-in-root--context" rel="nofollow noreferrer">this doc</a> . i added my plugins/packages globally as mentioned in <a href="https://stackoverflow.com/questions/66831561/nuxt-add-global-plugins-memory-leakage-issue/66834247#66834247">this question</a> . So is the <code>vue.use()</code> a problem ? should i use <code>inject</code> instead? how?</p>
  <pre class="lang-js prettyprint-override"><code>// vue-product-zoomer package
  import Vue from 'vue'
  import ProductZoomer from 'vue-product-zoomer'
  Vue.use(ProductZoomer)
  </code></pre>
  <p><strong># 5 - Vee Validate leakage :</strong>
  I read <a href="https://github.com/logaretm/vee-validate/issues/1587#issuecomment-421339419" rel="nofollow noreferrer">here</a> about it , is this really cause leakage? I'm using Vee Validate v3 :</p>
  <p>my veevalidate.js that added globally to nuxt.config.js</p>
  <pre class="lang-js prettyprint-override"><code>import Vue from 'vue'
  import {  ValidationObserver, ValidationProvider, setInteractionMode } from 'vee-validate'
  import { localize } from 'vee-validate';
  import en from 'vee-validate/dist/locale/en.json';
  import fa from 'vee-validate/dist/locale/fa.json';
  
  localize({
      en,
      fa
  });
  
  setInteractionMode('eager')
  
  let LOCALE = &quot;fa&quot;;
  Object.defineProperty(Vue.prototype, &quot;locale&quot;, {
      configurable: true,
      get() {
          return LOCALE;
      },
      set(val) {
          LOCALE = val;
          localize(val);
      }
  });
  
  Vue.component('ValidationProvider', ValidationProvider);
  Vue.component(&quot;ValidationObserver&quot;, ValidationObserver);
  </code></pre>
  <p>my veevalidate mixin that added to each page/component had use veevalidate . ( I used a mixin because I needed to use my vuex state <code>lang</code> )</p>
  <pre class="lang-js prettyprint-override"><code>
  import { required, email , alpha , alpha_spaces , numeric , confirmed , password } from 'vee-validate/dist/rules'
  import { extend } from 'vee-validate'
  
  export default {
      mounted() {
          extend(&quot;required&quot;, {
              ...required,
              message: `{_field_} ${this.lang.error_required}`
          });
          extend(&quot;email&quot;, {
              ...email,
              message: `{_field_} ${this.lang.error_email}`
          });
          extend(&quot;alpha&quot;, {
              ...alpha,
              message: `{_field_} ${this.lang.error_alpha}`
          });
          extend(&quot;alpha_spaces&quot;, {
              ...alpha_spaces,
              message: `{_field_} ${this.lang.error_alpha_spaces}`
          });
          extend(&quot;numeric&quot;, {
              ...numeric,
              message: `{_field_} ${this.lang.error_numeric}`
          });
          extend(&quot;confirmed&quot;, {
              ...confirmed,
              message: `{_field_} ${this.lang.error_confirmed}`
          });
          extend(&quot;decimal&quot;, {
              validate: (value, { decimals = '*', separator = '.' } = {}) =&gt; {
                  if (value === null || value === undefined || value === '') {
                      return {
                          valid: false
                      };
                  }
                  if (Number(decimals) === 0) {
                      return {
                          valid: /^-?\d*$/.test(value),
                      };
                  }
                  const regexPart = decimals === '*' ? '+' : `{1,${decimals}}`;
                  const regex = new RegExp(`^[-+]?\\d*(\\${separator}\\d${regexPart})?([eE]{1}[-]?\\d+)?$`);
          
                  return {
                      valid: regex.test(value),
                  };
              },
              message: `{_field_} ${this.lang.error_decimal}`
          })
      }
  }
  </code></pre>
  <p><strong># 6 - Keep-Alive :</strong>
  As I mentioned before I'm using keep-alive in my app and that it self cache many things and may not destroy/remove plugins and event listeners.</p>
  <p><strong># 7 - setTimeout :</strong>
  is there any need to use clearTimeout to do data clearing ??</p>
  <p><strong># 8 - Remove Plugins/Packages :</strong>
  in <a href="https://v2.vuejs.org/v2/cookbook/avoiding-memory-leaks.html" rel="nofollow noreferrer">this Doc</a> it is mentioned that some plugins/packages won't be removed even after component being destroyed , how can I find those ??</p>
  <p>here are my packages and nuxt.config</p>
  <pre class="lang-js prettyprint-override"><code>// package.json
  {
    &quot;name&quot;: &quot;nuxt&quot;,
    &quot;version&quot;: &quot;1.0.0&quot;,
    &quot;private&quot;: true,
    &quot;scripts&quot;: {
      &quot;dev&quot;: &quot;nuxt&quot;,
      &quot;build&quot;: &quot;nuxt build&quot;,
      &quot;start&quot;: &quot;nuxt start&quot;,
      &quot;generate&quot;: &quot;nuxt generate&quot;
    },
    &quot;dependencies&quot;: {
      &quot;@nuxt/http&quot;: &quot;^0.6.0&quot;,
      &quot;@nuxtjs/auth&quot;: &quot;^4.9.1&quot;,
      &quot;@nuxtjs/axios&quot;: &quot;^5.11.0&quot;,
      &quot;@nuxtjs/device&quot;: &quot;^1.2.7&quot;,
      &quot;@nuxtjs/google-gtag&quot;: &quot;^1.0.4&quot;,
      &quot;@nuxtjs/gtm&quot;: &quot;^2.4.0&quot;,
      &quot;chart.js&quot;: &quot;^2.9.3&quot;,
      &quot;cookie-universal-nuxt&quot;: &quot;^2.1.4&quot;,
      &quot;jquery&quot;: &quot;^3.5.1&quot;,
      &quot;less-loader&quot;: &quot;^6.1.2&quot;,
      &quot;nuxt&quot;: &quot;^2.13.0&quot;,
      &quot;nuxt-user-agent&quot;: &quot;^1.2.2&quot;,
      &quot;v-viewer&quot;: &quot;^1.5.1&quot;,
      &quot;vee-validate&quot;: &quot;^3.3.7&quot;,
      &quot;vue-chartjs&quot;: &quot;^3.5.0&quot;,
      &quot;vue-cropperjs&quot;: &quot;^4.1.0&quot;,
      &quot;vue-easy-dnd&quot;: &quot;^1.10.2&quot;,
      &quot;vue-glide-js&quot;: &quot;^1.3.14&quot;,
      &quot;vue-persian-datetime-picker&quot;: &quot;^2.2.0&quot;,
      &quot;vue-product-zoomer&quot;: &quot;^3.0.1&quot;,
      &quot;vue-slick-carousel&quot;: &quot;^1.0.6&quot;,
      &quot;vue-sweetalert2&quot;: &quot;^3.0.5&quot;,
      &quot;vue2-editor&quot;: &quot;^2.10.2&quot;,
      &quot;vuedraggable&quot;: &quot;^2.24.0&quot;,
      &quot;vuetify&quot;: &quot;^2.3.9&quot;
    },
    &quot;devDependencies&quot;: {
      &quot;@fortawesome/fontawesome-free&quot;: &quot;^5.15.1&quot;,
      &quot;@mdi/font&quot;: &quot;^5.9.55&quot;,
      &quot;@nuxtjs/dotenv&quot;: &quot;^1.4.1&quot;,
      &quot;css-loader&quot;: &quot;^3.6.0&quot;,
      &quot;flipclock&quot;: &quot;^0.10.8&quot;,
      &quot;font-awesome&quot;: &quot;^4.7.0&quot;,
      &quot;node-sass&quot;: &quot;^4.14.1&quot;,
      &quot;noty&quot;: &quot;^3.2.0-beta&quot;,
      &quot;nuxt-gsap-module&quot;: &quot;^1.2.1&quot;,
      &quot;sass-loader&quot;: &quot;^8.0.2&quot;
    }
  }
  </code></pre>
  <pre class="lang-js prettyprint-override"><code>//nuxt.config.js
  const env = require('dotenv').config()
  const webpack = require('webpack')
  
  export default {
    mode: 'universal',
  
    loading: {
      color: 'green',
      failedColor: 'red',
      height: '3px'
    },
    router: {
      // base: process.env.NUXT_BASE_URL || '/' 
    },
    head: {
      title: process.env.SITE_TITLE + ' | ' + process.env.SITE_SHORT_DESC || '',
      meta: [
        { charset: 'utf-8' },
        { name: 'viewport', content: 'width=device-width, initial-scale=1' },
        { hid: 'keywords', name: 'keywords', content: process.env.SITE_KEYWORDS || '' },
        { hid: 'description', name: 'description', content: process.env.SITE_DESCRIPTION || '' },
        { hid: 'robots', name: 'robots', content: process.env.SITE_ROBOTS || '' },
        { hid: 'googlebot', name: 'googlebot', content: process.env.SITE_GOOGLE_BOT || '' },
        { hid: 'bingbot', name: 'bingbot', content: process.env.SITE_BING_BOT || '' },
        { hid: 'og:locale', name: 'og:locale', content: process.env.SITE_OG_LOCALE || '' },
        { hid: 'og:type', name: 'og:type', content: process.env.SITE_OG_TYPE || '' },
        { hid: 'og:title', name: 'og:title', content: process.env.SITE_OG_TITLE || '' },
        { hid: 'og:description', name: 'og:description', content: process.env.SITE_OG_DESCRIPTION || '' },
        { hid: 'og:url', name: 'og:url', content: process.env.SITE_OG_URL || '' },
        { hid: 'og:site_name', name: 'og:site_name', content: process.env.SITE_OG_SITENAME || '' },
        { hid: 'theme-color', name: 'theme-color', content: process.env.SITE_THEME_COLOR || '' },
        { hid: 'msapplication-navbutton-color', name: 'msapplication-navbutton-color', content: process.env.SITE_MSAPP_NAVBTN_COLOR || '' },
        { hid: 'apple-mobile-web-app-status-bar-style', name: 'apple-mobile-web-app-status-bar-style', content: process.env.SITE_APPLE_WM_STATUSBAR_STYLE || '' },
        { hid: 'X-UA-Compatible', 'http-equiv': 'X-UA-Compatible', content: process.env.SITE_X_UA_Compatible || '' }
      ],
      link: [
        { rel: 'icon', type: 'image/x-icon', href: process.env.SITE_FAVICON },
        // { rel: 'shortcut icon', type: 'image/x-icon', href: process.env.SITE_FAVICON },
        { rel: 'canonical', href: process.env.SITE_REL_CANONICAL },
        // { rel: 'stylesheet', href: 'https://cdn.jsdelivr.net/npm/font-awesome@4.x/css/font-awesome.min.css' },
      ]
    },
    css: [
        '~/assets/scss/style.scss',
        '~/assets/scss/media.scss',
        '~/assets/scss/customization.scss',
        '~/assets/scss/sweetalert.scss',
        '~/assets/scss/noty.scss',
        '~/assets/scss/flipclock.scss',
        '~/assets/scss/glide.scss',
        '~/assets/scss/sorting.scss',
        '~/assets/scss/cropper.scss',
        '~/assets/scss/transitions.scss',
        '~/assets/scss/product-zoom.scss',
        'vue-slick-carousel/dist/vue-slick-carousel.css'
    ],
    plugins: [
        'plugins/mixins/reqerrors.js',
        'plugins/mixins/user.js',
        'plugins/mixins/language.js',
        'plugins/mixins/shopinfo.js',
        'plugins/mixins/formattedprice.js',
        'plugins/mixins/utils.js',
        'plugins/mixins/cms.js',
        'plugins/mixins/client.js',
        'plugins/mixins/cart.js',
        'plugins/axios.js',
        'plugins/veevalidate.js',
        'plugins/noty.js',
        'plugins/glide.js',
        '@plugins/vuetify',
        '@plugins/vuedraggable',
        '@plugins/vuedraggable',
        '@plugins/vue-slick-carousel.js',
        {src: 'plugins/vuepersiandatepicker.js', mode: 'client'},
        {src: 'plugins/cropper.js', mode: 'client'},
        {src: 'plugins/vue-product-zoomer.js', mode: 'client'},
        {src: 'plugins/vueeditor.js', mode: 'client'},
    ],
    buildModules: [
      '@nuxtjs/dotenv',
      'nuxt-gsap-module'
    ],
    modules: [
      '@nuxtjs/axios',
      '@nuxtjs/auth',
      '@nuxtjs/device',
      ['vue-sweetalert2/nuxt',
        {
          confirmButtonColor: '#29BF12',
          cancelButtonColor: '#FF3333'
        }
      ],
      'cookie-universal-nuxt',
      '@nuxtjs/gtm',
      '@nuxtjs/google-gtag',
      'nuxt-user-agent',
    ],
  
    gtm: {
      id: process.env.GOOGLE_TAGS_ID,
      debug: false
    },
    'google-gtag': {
      id: process.env.GOOGLE_ANALYTICS_ID,
      debug: false
    },
    gsap: {
      extraPlugins: {
        cssRule: false,
        draggable: false,
        easel: false,
        motionPath: false,
        pixi: false,
        text: false,
        scrollTo: false,
        scrollTrigger: false
      },
      extraEases: {
        expoScaleEase: false,
        roughEase: false,
        slowMo: true,
      }
    },
    axios: {
      baseURL: process.env.BASE_URL,
    },
    auth: {
        strategies: {
          local: {
            endpoints: {
              login: { url: 'auth/login', method: 'post', propertyName: 'token' },
              logout: { url: 'auth/logout', method: 'post' },
              user: { url: 'auth/info', method: 'get', propertyName: '' }
            }
          }
        },
        redirect: {
          login: '/login',
          home: '',
          logout: '/login'
        },
        cookie: {
          prefix: 'auth.',
          options: {
            path: '/',
            maxAge: process.env.AUTH_COOKIE_MAX_AGE
          }
        }
    },
  
    publicRuntimeConfig: {
      gtm: {
        id: process.env.GOOGLE_TAGS_ID
      },
      'google-gtag': {
        id: process.env.GOOGLE_ANALYTICS_ID,
      }
    },
    build: {
      transpile: ['vee-validate/dist/rules'],
      plugins: [
        new webpack.ProvidePlugin({
          '$': 'jquery',
          jQuery: &quot;jquery&quot;,
          &quot;window.jQuery&quot;: &quot;jquery&quot;,
          '_': 'lodash'
        }),
        new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
      ],
      postcss: {
        preset: {
          features: {
            customProperties: false,
          },
        },
      },
      loaders: {
        scss: {
          prependData: `$theme_colors: (&quot;theme_body_color&quot;:&quot;${process.env.THEME_BODY_COLOR}&quot;,&quot;theme_main_color&quot;:&quot;${process.env.THEME_MAIN_COLOR}&quot;,&quot;theme_main_color2&quot;:&quot;${process.env.THEME_MAIN_COLOR2}&quot;,&quot;theme_side_color&quot;:&quot;${process.env.THEME_SIDE_COLOR}&quot;,&quot;theme_side_color2&quot;:&quot;${process.env.THEME_SIDE_COLOR2}&quot;,&quot;theme_link_color&quot;:&quot;${process.env.THEME_LINK_COLOR}&quot;);`
        }
      },
    }
  }
  
  </code></pre>