Nuxt huge memory usage / leakage and how to prevent - vue.js

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.
Please answer each one according to their #numbers
#1 - NuxtLink (vue-router) memory leakage :
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:
<template>
my page link
</template>
<script>
export default{
methods:{
goTo(link){
this.$router.push(link)
}
}
}
</script>
what do you think about this approach ?? and what about Vuetify to props as they work like nuxt-link?
<template>
<v-card to="/mypage" ></v-card>
</template>
#2 - Dynamic component load :
As my app is bidirectional and customizable by .env file , i had to lazy load many of my components dynamically and conditionally like this:
<template>
<component :is="mycomp" />
</template>
<script>
export default{
computed:{
mycomp(){
return import()=>(`#/components/${process.env.SITE_DIR}/mycomp.vue`)
}
}
}
</script>
will this cause high memory usage/leakage ??
# 3 - Nuxt Event Bus :
beside normal this.$emit() in my components, sometimes I had to use $nuxt.$emit() . i remove them all in beforeDestroy hook :
<script>
export default{
created:{
this.$nuxt.$on('myevent', ()=>{
// do something
}
},
beforeDestroy(){
this.$nuxt.$off('myevent')
}
}
</script>
but someone told me that listeners on created hook will be SSR and won't be removed in CSR beforeDestroy hook. so what should i do? add if(process.client){} to created ??
# 4 - Global Plugins :
I found this issue and also this doc . i added my plugins/packages globally as mentioned in this question . So is the vue.use() a problem ? should i use inject instead? how?
// vue-product-zoomer package
import Vue from 'vue'
import ProductZoomer from 'vue-product-zoomer'
Vue.use(ProductZoomer)
# 5 - Vee Validate leakage :
I read here about it , is this really cause leakage? I'm using Vee Validate v3 :
my veevalidate.js that added globally to nuxt.config.js
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 = "fa";
Object.defineProperty(Vue.prototype, "locale", {
configurable: true,
get() {
return LOCALE;
},
set(val) {
LOCALE = val;
localize(val);
}
});
Vue.component('ValidationProvider', ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
my veevalidate mixin that added to each page/component had use veevalidate . ( I used a mixin because I needed to use my vuex state lang )
import { required, email , alpha , alpha_spaces , numeric , confirmed , password } from 'vee-validate/dist/rules'
import { extend } from 'vee-validate'
export default {
mounted() {
extend("required", {
...required,
message: `{_field_} ${this.lang.error_required}`
});
extend("email", {
...email,
message: `{_field_} ${this.lang.error_email}`
});
extend("alpha", {
...alpha,
message: `{_field_} ${this.lang.error_alpha}`
});
extend("alpha_spaces", {
...alpha_spaces,
message: `{_field_} ${this.lang.error_alpha_spaces}`
});
extend("numeric", {
...numeric,
message: `{_field_} ${this.lang.error_numeric}`
});
extend("confirmed", {
...confirmed,
message: `{_field_} ${this.lang.error_confirmed}`
});
extend("decimal", {
validate: (value, { decimals = '*', separator = '.' } = {}) => {
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}`
})
}
}
# 6 - Keep-Alive :
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.
# 7 - setTimeout :
is there any need to use clearTimeout to do data clearing ??
# 8 - Remove Plugins/Packages :
in this Doc it is mentioned that some plugins/packages won't be removed even after component being destroyed , how can I find those ??
here are my packages and nuxt.config
// package.json
{
"name": "nuxt",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate"
},
"dependencies": {
"#nuxt/http": "^0.6.0",
"#nuxtjs/auth": "^4.9.1",
"#nuxtjs/axios": "^5.11.0",
"#nuxtjs/device": "^1.2.7",
"#nuxtjs/google-gtag": "^1.0.4",
"#nuxtjs/gtm": "^2.4.0",
"chart.js": "^2.9.3",
"cookie-universal-nuxt": "^2.1.4",
"jquery": "^3.5.1",
"less-loader": "^6.1.2",
"nuxt": "^2.13.0",
"nuxt-user-agent": "^1.2.2",
"v-viewer": "^1.5.1",
"vee-validate": "^3.3.7",
"vue-chartjs": "^3.5.0",
"vue-cropperjs": "^4.1.0",
"vue-easy-dnd": "^1.10.2",
"vue-glide-js": "^1.3.14",
"vue-persian-datetime-picker": "^2.2.0",
"vue-product-zoomer": "^3.0.1",
"vue-slick-carousel": "^1.0.6",
"vue-sweetalert2": "^3.0.5",
"vue2-editor": "^2.10.2",
"vuedraggable": "^2.24.0",
"vuetify": "^2.3.9"
},
"devDependencies": {
"#fortawesome/fontawesome-free": "^5.15.1",
"#mdi/font": "^5.9.55",
"#nuxtjs/dotenv": "^1.4.1",
"css-loader": "^3.6.0",
"flipclock": "^0.10.8",
"font-awesome": "^4.7.0",
"node-sass": "^4.14.1",
"noty": "^3.2.0-beta",
"nuxt-gsap-module": "^1.2.1",
"sass-loader": "^8.0.2"
}
}
//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: "jquery",
"window.jQuery": "jquery",
'_': 'lodash'
}),
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/)
],
postcss: {
preset: {
features: {
customProperties: false,
},
},
},
loaders: {
scss: {
prependData: `$theme_colors: ("theme_body_color":"${process.env.THEME_BODY_COLOR}","theme_main_color":"${process.env.THEME_MAIN_COLOR}","theme_main_color2":"${process.env.THEME_MAIN_COLOR2}","theme_side_color":"${process.env.THEME_SIDE_COLOR}","theme_side_color2":"${process.env.THEME_SIDE_COLOR2}","theme_link_color":"${process.env.THEME_LINK_COLOR}");`
}
},
}
}

I think it is time to share my understanding (even though it's little):
#1 as vue-router use prefetch there may be heavy memory usage depending on number of links. in my case there are not many so I let them be, there is also an option to disable prefetch in nuxt so if your app is super busy or you have hundreds of links in a single page better to disable prefetch:
// locally
<nuxt-link to="/" no-prefetch>link</nuxt-link>
// globally in nuxt.config.js
router: {
prefetchLinks: false
}
#2 I didn't found any problem with dynamic components
#3 not with $nuxt.$on but I experienced it (event listener not being removed) when used window.addEventListener in created hook. so better moved all listeners to client side (beforeMount or mounted) as much as possible
#4 as I mentioned in a comment above I removed global plugins/css as much as I could for a lighter init and used them locally , but about Vue.use() memory leakage, that was my misunderstanding !! in nuxt doc is said that:
Don't use Vue.use(), Vue.component(), and globally, don't plug anything in Vue inside this function, dedicated to Nuxt injection. It will cause memory leak on server-side.
So using Vue.use() inside injection function may cause memory leakage not Vue.use() itself.
As for others still no answer

#6 Is a bad choice. Keep-alive is an engine that can be used in some way. Cache on Component level and on path level can reduce RAM usage too. 4GB of RAM is used for something, we need more in-depth knowledge.
#7 In the future yes - there will be more optimization as part of the framework and then progressive nature.
#8 From documentation
Memory leaks in Vue applications do not typically come from Vue itself, rather they can happen when incorporating other libraries into an application.
This is why it is hard to diagnose. You can use the Performance tab to find scripts that leak data as this is part of the described issue. Second part is cache (localCache, sessionCache and ServiceWorker) and thus not feasible to describe a simple method to remove script.
Most important: scope of vue is a component so this can be a strategy to disable one by one all stuff to diagnose.

Related

<nuxt-img/> Is not working with v-bind:src coming from an API response

The documentation mentions that I can use <nuxt-img/> like I'm using the HTML's <img> tag however this is not the case.
I have made this example to demonstrate that <img> tag is working just fine while <nuxt-img/> is not displaying the image.
This is the code:
<template>
<main>
<pre>{{ pokemon.sprites.front_shiny }}</pre>
<h1>Normal Image Tag</h1>
<img class="normal-img-tag" :src="`${pokemon.sprites.front_shiny}`" />
<h1>Nuxt Image Tag</h1>
<nuxt-img
class="nuxt-img-tag"
placeholder="/images/lazy.jpg"
:src="`${pokemon.sprites.front_shiny}`"
/>
</main>
</template>
<script>
export default {
data: () => ({
pokemon: {},
}),
async fetch() {
this.pokemon = await this.$axios.$get(
"https://pokeapi.co/api/v2/pokemon/charizard"
);
},
};
</script>
nuxt.config.js
export default {
target: 'static',
head: {
title: 'nuxt-img',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
modules: [
'#nuxtjs/axios',
'#nuxt/image',
],
image: {
domains: ['localhost']
},
axios: {
baseURL: '/',
},
}
package.json
{
"name": "nuxt-img",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate"
},
"dependencies": {
"#nuxtjs/axios": "^5.13.6",
"core-js": "^3.25.3",
"nuxt": "^2.15.8",
"vue": "^2.7.10",
"vue-server-renderer": "^2.7.10",
"vue-template-compiler": "^2.7.10"
},
"devDependencies": {
"#nuxt/image": "^0.7.1"
}
}
Here is a screenshot
that showns that Image is the lazy load image specified inside nuxt-img so nuxt-image is actually working but :src is not.
UPDATE
I have added :
image: {
domains: ['https://raw.githubusercontent.com'],
}
to my nuxt.config.js as #kissu mentioned but I get this error on console :
GET http://localhost:3000/_ipx/_/https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/6.png 500 (IPX Error (500))
and in terminal this error :
ERROR Not supported 11:21:15
at getExport (node_modules\ohmyfetch\cjs\node.cjs:1:54)
at Object.fetch (node_modules\ohmyfetch\cjs\node.cjs:2:47)
at Object.http (node_modules\ipx\dist\shared\ipx.eadce322.cjs:142:38)
at node_modules\ipx\dist\shared\ipx.eadce322.cjs:445:33
at Object.src (node_modules\ipx\dist\shared\ipx.eadce322.cjs:69:25)
at _handleRequest (node_modules\ipx\dist\shared\ipx.eadce322.cjs:521:25)
at handleRequest (node_modules\ipx\dist\shared\ipx.eadce322.cjs:549:10)
at IPXMiddleware (node_modules\ipx\dist\shared\ipx.eadce322.cjs:565:12)
at call (node_modules\connect\index.js:239:7)
at next (node_modules\connect\index.js:183:5)
at next (node_modules\connect\index.js:161:14)
at WebpackBundler.middleware (node_modules\#nuxt\webpack\dist\webpack.js:2194:5)
PROJECT REPO ON GITHUB :
https://github.com/abdurrahmanseyidoglu/nuxt-img-test
Am I doing something wrong or it actually does not work this way?
This will probably work if you try with the following
<template>
<main v-if="!$fetchState.pending">
<pre>{{ pokemon.sprites.front_shiny }}</pre>
<h1>Normal Image Tag</h1>
<h1>Nuxt Image Tag</h1>
<nuxt-img class="nuxt-img-tag" :src="pokemon.sprites.front_shiny" />
</main>
</template>
<script>
export default {
data() {
return {
pokemon: {},
}
},
async fetch() {
this.pokemon = await this.$axios.$get(
'https://pokeapi.co/api/v2/pokemon/charizard'
)
console.log('poke', this.pokemon)
},
}
</script>
You need to have that one in the nuxt.config.js file
export default {
modules: ['#nuxt/image', '#nuxtjs/axios'],
image: {
domains: ['https://raw.githubusercontent.com'],
},
}
since it's referring an external website.
To enable image optimization on an external website, specify which domains are allowed to be optimized.
Here is the doc related: https://image.nuxtjs.org/api/options#domains
In your nuxt.config you've specified domains option for image module. Add your API domain to the array

Nuxt.js: 404 code in production on some dynamic routes

My Nuxt project has around 700-1000 static and dynamic pages hosted via Netlify. ~300 of them are generated correctly.
In production, I discovered that certain dynamically generates routes get a HTTP 404 status code. However, they are generated and loaded without any other error in production. They just get the 404 error code which has bad implications for SEO. Dev server and running the generated dist folder via nuxt start locally both show no 404 code on those pages. Static generated pages work fine.
Here are my settings for nuxt.config.js (see full list below)
target: 'static',
generate: {
fallback: true,
....
}
I do not specify the dynamic routes on generate() because of the nuxt crawler .
package.json:
{
"name": "xxxxxxx",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"start": "nuxt start",
"generate": "nuxt generate",
"generate_coin_list_file": "node scripts/generate_coin_list.js",
"generate_coin_images": "node scripts/download_coin_images.js"
},
"dependencies": {
"#nuxt/content": "^1.14.0",
"#nuxtjs/axios": "^5.13.6",
"#nuxtjs/gtm": "^2.4.0",
"#nuxtjs/proxy": "^2.1.0",
"#nuxtjs/sitemap": "^2.4.0",
"core-js": "^3.15.1",
"frontmatter-markdown-loader": "^3.7.0",
"ipx": "^0.9.4",
"lite-youtube-embed": "^0.2.0",
"nuxt": "^2.15.8",
"nuxt-jsonld": "^1.5.3",
"v-click-outside": "^3.2.0",
"vue-disqus": "^4.0.1"
},
"devDependencies": {
"#nuxt/image": "^0.6.2",
"#nuxtjs/tailwindcss": "^4.2.0",
"eslint-config-prettier": "^8.3.0",
"postcss": "^8.3.5",
"prettier": "^2.3.2"
}
}
I am not sure where to look for since this error does not seem to follow a pattern. Because I can generate it locally without the error it could be problem with Netlify. What do you guys think of this? Thank you!
Example:
_slug.vue
export default {
name: 'BlogSlug',
async asyncData({ $content, params, route, error }) {
let author = {}
let article = await $content('articles', { deep: true })
.where({
slug: params.slug,
})
.fetch()
.catch(() => {
error({ statusCode: 404, message: 'Page not found' })
})
article = article[0]
const allArticles = await $content('articles', {
deep: true,
})
.sortBy('date', 'desc')
.limit(6)
.fetch()
.catch(() => {
error({ statusCode: 404, message: 'Page not found' })
})
if (article.author) {
author = await $content('authors')
.where({
id: article.author,
})
.fetch()
.catch(() => {
error({ statusCode: 404, message: 'Page not found' })
})
}
return { article, allArticles, author }
},
...
}
nuxt.config.js
import coinList from './data/coin_list.json'
export default {
target: 'static',
head: {
title: 'XXXXXX',
htmlAttrs: {
lang: 'de',
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: '' },
{ name: 'format-detection', content: 'telephone=no' },
{
hid: 'twitter:card',
property: 'twitter:card',
content: 'summary_large_image',
},
],
link: [
{
hid: 'apple-touch-icon',
rel: 'apple-touch-icon',
sizes: '180x180',
href: '/favicon.ico',
},
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' },
],
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: [
'~/assets/css/tailwind.css',
'node_modules/lite-youtube-embed/src/lite-yt-embed.css',
],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
'~/plugins/metadata',
'~/plugins/youtube.client.js',
'~/plugins/jsonLd.js',
{ src: '#/plugins/vClickOutside', ssr: false },
'~/plugins/disqus',
],
// Auto import components: https://go.nuxtjs.dev/config-components
components: [
'~/components',
{ path: '~/components/utils', extensions: ['vue'] },
{ path: '~/components/global' },
],
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
// https://go.nuxtjs.dev/tailwindcss
'#nuxtjs/tailwindcss',
[
'#nuxt/image',
{
provider: 'static',
},
],
'#/modules/sitemapRouteGenerator.js',
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
'#nuxtjs/axios',
'#nuxt/content',
'#nuxtjs/proxy',
'#nuxtjs/gtm',
// has to be last
'#nuxtjs/sitemap',
],
sitemap: {
hostname: process.env.NUXT_ENV_BASE_URL,
path: '/sitemap.xml',
},
// GTM Analytics
gtm: {
enabled: true,
pageTracking: true,
},
axios: {},
image: {
// Options
domains: ['assets.coingecko.com', 'coingecko.com'],
presets: {
blog: {
modifiers: {
format: 'webp',
},
},
},
},
// Content module configuration: https://go.nuxtjs.dev/config-content
content: {
// nestedProperties: ['articles.slug'],
},
generate: {
fallback: true,
async routes() {
const routes = await _getRoutes()
async function _getRoutes($content) {
const paths = []
coinList.forEach((coin) => {
paths.push({
route: `/coins/${coin.id}/historisch/`,
payload: coin.id,
})
})
return paths
}
return routes
},
},
router: {
routeNameSplitter: '/',
},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {
extend(config) {
config.module.rules.push({
test: /\.md$/,
loader: 'frontmatter-markdown-loader',
})
},
},
}
Module: sitemapRouteGenerator.js
export default function () {
this.nuxt.hook('generate:done', (context) => {
const routesToExclude = /\/index|\/articles\/|\/undefined/ // Add any route you don't want in your sitemap. Potentially get this from an .env file.
const allRoutes = Array.from(context.generatedRoutes)
// console.log(context.generatedRoutes)
const routes = allRoutes.filter((route) => !routesToExclude.test(route))
// console.log(routes)
this.nuxt.options.sitemap.routes = [...routes]
})
}
I solved the issue. The fix was to generate all routes of dynamic pages in the generate hook even though Nuxt states about the generate crawler:
Since Nuxt 2.14+, nuxt generate has a crawler feature integrated which will crawl all your links and generate your routes based on those links. Therefore you do not need to do anything in order for your dynamic routes to be crawled.
I guess it only works on static pages and partly on dynamic routes (?). Since I used a nested directory structure I had to modify the route path before manually.
generate: {
fallback: true,
async routes() {
let routes
const contentRoutes = await _getContentRoutes()
async function _getContentRoutes() {
const { $content } = require('#nuxt/content')
const files = await $content({ deep: true }).only(['path']).fetch()
return files.map((file) => file.path.replace('/articles/', ''))
}
return routes
},
},
In this example I have some markdown files in content/articles/SOMEARTICLE.md so I had to remove the '/article' from the path. Otherwise nuxt throws an error when generating the routes.

Nuxt.js Oauth sometimes crashes whole webpage

I have created Nuxt.js application, I decided to build in Nuxt/auth module, everything works fine in web browsers, but somethimes when user navigates with mobile browser my application is crushed, simply it don't respond nothing, also there is no api calls, but after one refresh everything works fine, I can not guess what's happening, I could not find anything in the resources available on the Internet.
const axios = require('axios')
export default {
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: 'app',
htmlAttrs: {
lang: 'en'
},
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
],
script: [
// { src: "//code-eu1.jivosite.com/widget/UoBOrMfSmm", async: true },
]
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: [ '~/assets/css/transition.css', '~/assets/css/errors.css' ],
pageTransition: "fade",
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [
{ src: "~/plugins/star-rating", ssr: false },
{ src: "~/plugins/mask", ssr: false },
{ src: "~/plugins/rangeSlider", ssr: false },
{ src: "~/plugins/vueSelect", ssr: false },
{ src: "~/plugins/vuelidate", ssr: false },
],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [
[ '#nuxtjs/google-analytics', {
id: 'xxx'
} ]
],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
'bootstrap-vue/nuxt',
'#nuxtjs/axios',
'#nuxtjs/toast',
'#nuxtjs/auth-next',
[ 'nuxt-lazy-load', {
defaultImage: '/spin2.gif'
} ],
[ 'nuxt-facebook-pixel-module', {
/* module options */
track: 'PageView',
pixelId: '',
autoPageView: true,
disabled: false
} ],
'nuxt-moment',
'#nuxtjs/robots',
'#nuxtjs/sitemap'
],
moment: {
locales: ['ru', 'en']
},
toast: {
position: 'top-center',
},
robots: [
{
UserAgent: '*',
Disallow: ['/user', '/admin'],
},
],
axios: {
baseURL: 'https://api.test.com/', // Used as fallback if no runtime config is provided
},
sitemap:{
exclude:[
'/user',
'/admin',
'/admin/*',
'/user/*',
],
defaults: {
changefreq: 'daily',
priority: 1,
lastmod: new Date()
},
routes: async () => {
const { data } = await axios.get('https://api.test.com/api/cars/all')
return data.map((product) => `https://test.com/product/${product.id}/${product.name}`)
}
},
loading: {
color: '#F48245',
height: '4px'
},
target: 'server',
/* auth */
auth: {
plugins:[
{ src: "~/plugins/providers", ssr:false},
],
redirect: {
login: '/',
logout: '/',
home: '/',
callback: '/callback'
},
strategies: {
local: {
token: {
property: 'user.token',
},
user: {
property: false
},
endpoints: {
login: { url: 'api/login', method: 'post' },
logout: { url: 'api/logout', method: 'post' },
user: { url: 'api/user', method: 'get' }
},
},
facebook: {
endpoints: {
userInfo: 'https://graph.facebook.com/v6.0/me?fields=id,name,picture{url}',
},
redirectUri:'xxx',
clientId: '184551510189971',
scope: ['public_profile', 'email'],
},
google: {
responseType: 'token id_token',
codeChallengeMethod: '',
clientId: 'xxx',
redirectUri: 'https://test.com/callback',
scope: ['email'],
},
},
cookie: {
prefix: 'auth.',
},
},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {},
};
This is my plugins directory file, where i am handling client oauth process.
export default async function ({ app }) {
console.log('auth executed')
if (!app.$auth.loggedIn) {
return
} else {
console.log('auth executed inside loop')
const auth = app.$auth;
const authStrategy = auth.strategy.name;
if (authStrategy === 'facebook') {
let data2 = {
fb_token: auth.user.id,
first_name: auth.user.name
}
try {
const response = await app.$axios.$post("/api/oauth", data2);
await auth.setStrategy('local');
await auth.strategy.token.set("Bearer " + response.user.token);
await auth.fetchUser();
} catch (e) {
console.log(e);
}
} else if (authStrategy === 'google') {
let dataGoogle = {
google_token: auth.user.sub,
first_name: auth.user.given_name,
last_name:auth.user.family_name
}
try {
const response = await app.$axios.$post("/api/oauth", dataGoogle);
await auth.setStrategy('local');
await auth.strategy.token.set("Bearer " + response.user.token);
await auth.fetchUser();
} catch (e) {
console.log(e);
}
}
}
}
For any issues related to DOM hydration, you can check my answer here: https://stackoverflow.com/a/67978474/8816585
It does have several possible cases (dynamic content with a difference between client side and server side rendered template, some random functions, purely wrong HTML structure etc...) and also a good blog article from Alex!

error "window is not defined " when using plugin Vue.js

Just learning vue. Install the slider plugin for the site from here: https://github.com/surmon-china/vue-awesome-swiper . Transferred the code and received such an error: 'window is not defined' Code below. I use nuxt.js. There are several solutions on the Internet, but none of them helped me.
slider.vue
<script>
import 'swiper/dist/css/swiper.css'
import { swiper, swiperSlide } from 'vue-awesome-swiper';
if (process.browser) {
const VueAwesomeSwiper = require('vue-awesome-swiper/dist/ssr')
Vue.use(VueAwesomeSwiper)
}
export default {
components: {
swiper,
swiperSlide
},
data() {
return {
swiperOption: {
slidesPerView: 'auto',
centeredSlides: true,
spaceBetween: 30,
pagination: {
el: '.swiper-pagination',
clickable: true
}
}
}
}
}
</script>
vue-awesome-swiper.js
import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper'
// require styles
import 'swiper/dist/css/swiper.css'
Vue.use(VueAwesomeSwiper,{});
nuxt.config.js
module.exports = {
/*
** Headers of the page
*/
head: {
title: 'stud-cit',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: 'Stud-cit site' }
],
link: [
{ rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }
]
},
loading: { color: 'blue'},
plugins: [
'~/plugins/vuetify',
'~/plugins/vue-awesome-swiper' ,
'~/plugins/vuePose'
],
build: {
vendor :['vue-awesome-swiper/dist/ssr'],
extend (config, { isDev, isClient }) {
if (isDev && isClient) {
config.module.rules.push({
enforce: 'pre',
test: /\.(js|vue)$/,
loader: 'eslint-loader',
exclude: /(node_modules)/
});
}
}
}
};
This library has special build for SSR.
Reference
import Vue from 'vue'
import VueAwesomeSwiper from 'vue-awesome-swiper/dist/ssr'
Vue.use(VueAwesomeSwiper)

ESLint throw error Expected method shorthand (object shorthand)

I am following the steps in this tutorial :
https://www.youtube.com/watch?v=z6hQqgvGI4Y
using VSCode (version 1.22.2) as my editor
I am running the following version
==> vue --version
2.9.3
of Vue / vue-cli installed from npm using the steps outlined here :
npm install --global vue-cli
My VSCode workspace settings (User settings) are as follows :
{
"workbench.colorTheme": "Visual Studio Dark",
"window.zoomLevel": 1,
"workbench.statusBar.visible": true,
"workbench.startupEditor": "newUntitledFile",
// Format a file on save. A formatter must be available, the file must not be auto-saved, and editor must not be shutting down.
// "editor.formatOnSave": true,
"eslint.autoFixOnSave": true,
// Enable/disable default JavaScript formatter (For Prettier)
"javascript.format.enable": false,
// Use 'prettier-eslint' instead of 'prettier'. Other settings will only be fallbacks in case they could not be inferred from eslint rules.
"prettier.eslintIntegration": false,
"editor.insertSpaces": true,
"[javascript]": {
"editor.tabSize": 2,
"editor.insertSpaces": true
},
"[vue]": {
"editor.tabSize": 2,
"editor.insertSpaces": true
},
"eslint.options": {
"extensions": [".html", ".js", ".vue", ".jsx"]
},
"eslint.validate": [
{
"language": "html",
"autoFix": true
},
{
"language": "vue",
"autoFix": false
},
{
"language": "javascript",
"autoFix": true
},
{
"language": "javascriptreact",
"autoFix": true
}
]
}
I have the Vetur tooling for VSCode installed :
https://github.com/vuejs/vetur
I have the following files :
src/components/HomeCentral.vue
<template>
<div class="homecentral">
<input type="text" v-model="title"><br/>
<h1>{{title}}</h1>
<p v-if="showName">{{user.first_name}}</p>
<p v-else>Nobody</p>
<ul>
<li v-for="item in items" :key="item.id">{{item.title}}</li>
</ul>
<button v-on:click="greet('Hello World')">Say Greeting</button>
</div>
</template>
<script>
export default {
name: 'HomeCentral',
data() {
return {
title: 'Welcome',
user: {
first_name: 'John',
last_name: 'Doe',
},
showName: true,
items: [
{ title: 'Item One' },
{ title: 'Item Two' },
{ title: 'Item Three' },
],
};
},
methods: {
greet: function (greeting) {
alert(greeting);
},
},
};
</script>
<style scoped>
</style>
src/App.vue
<template>
<div id="app">
<home-central></home-central>
</div>
</template>
<script>
import HomeCentral from './components/HomeCentral';
export default {
name: 'App',
components: {
HomeCentral,
},
};
</script>
<style>
#app {
font-family: "Avenir", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
src/router/index.js
import Vue from 'vue';
import Router from 'vue-router';
import HomeCentral from '../components/HomeCentral';
Vue.use(Router);
export default new Router({
routes: [
{
path: '/',
name: 'HomeCentral',
component: HomeCentral,
},
],
});
My .eslintrc looks as follows :
// https://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
env: {
browser: true,
node: true,
"es6": false
},
extends: 'airbnb-base',
// required to lint *.vue files
plugins: [
'html',
'vue'
],
// check if imports actually resolve
settings: {
'import/resolver': {
webpack: {
config: 'build/webpack.base.conf.js'
}
}
},
// add your custom rules here
rules: {
// don't require .vue extension when importing
'import/extensions': ['error', 'always', {
js: 'never',
vue: 'never'
}],
// disallow reassignment of function parameters
// disallow parameter object manipulation except for specific exclusions
'no-param-reassign': ['error', {
props: true,
ignorePropertyModificationsFor: [
'state', // for vuex state
'acc', // for reduce accumulators
'e' // for e.returnvalue
]
}],
// allow optionalDependencies
'import/no-extraneous-dependencies': ['error', {
optionalDependencies: ['test/unit/index.js']
}],
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
}
My .editorconfig looks like this :
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
But when I run
==> npm run dev
I get the following output :
> webpack-dev-server --inline --progress --config build/webpack.dev.conf.js
95% emitting
WARNING Compiled with 1 warnings 3:01:35 PM
⚠ http://eslint.org/docs/rules/func-names Unexpected unnamed method 'greet'
src/components/HomeCentral.vue:33:12
greet: function (greeting) {
^
⚠ http://eslint.org/docs/rules/no-alert Unexpected alert
src/components/HomeCentral.vue:34:7
alert(greeting);
^
✘ http://eslint.org/docs/rules/object-shorthand Expected method shorthand
src/components/HomeCentral.vue:33:5
greet: function (greeting) {
^
✘ 3 problems (1 error, 2 warnings)
Errors:
1 http://eslint.org/docs/rules/object-shorthand
Warnings:
1 http://eslint.org/docs/rules/no-alert
1 http://eslint.org/docs/rules/func-names
You may use special comments to disable some warnings.
Use // eslint-disable-next-line to ignore the next line.
Use /* eslint-disable */ to ignore all warnings in a file.
Why is ESlint complaining about "Expected method shorthand" as an error and pointing to the following ES6 linting rule :
http://eslint.org/docs/rules/object-shorthand
Does 2.9.3 version of Vue use ES6 ?
How to silence the VScode editor from linting this semantically correct Vue code :
Fixed by following PeterVojtek answer at the following :
https://github.com/vuejs-templates/webpack/issues/73
Basically changed the following section on build/webpack.base.conf.js
const createLintingRule = () => ({
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [resolve('src'), resolve('test')],
options: {
formatter: require('eslint-friendly-formatter'),
emitWarning: !config.dev.showEslintErrorsInOverlay
}
})
to
const createLintingRule = () => ({
})
Also removed 'html' from plugins sections of .eslintrc.js
// https://eslint.org/docs/user-guide/configuring
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
env: {
browser: true,
node: true,
"es6": false
},
extends: [
'airbnb-base',
],
// required to lint *.vue files
plugins: [
'vue',
],
// check if imports actually resolve
settings: {
'import/resolver': {
webpack: {
config: 'build/webpack.base.conf.js'
}
}
},
// add your custom rules here
rules: {
// don't require .vue extension when importing
'import/extensions': ['error', 'always', {
js: 'never',
vue: 'never'
}],
// disallow reassignment of function parameters
// disallow parameter object manipulation except for specific exclusions
'no-param-reassign': ['error', {
props: true,
ignorePropertyModificationsFor: [
'state', // for vuex state
'acc', // for reduce accumulators
'e' // for e.returnvalue
]
}],
// allow optionalDependencies
'import/no-extraneous-dependencies': ['error', {
optionalDependencies: ['test/unit/index.js']
}],
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
}
TSlint error/warning in callback function in 'Angular 10', VS code version 1.53.2 -
Solution -
doc.html(htmlData.innerHTML, {
callback(data: any): void {
data.save('angular-demo.pdf');
},
x: 10,
y: 10
});
package.json -
{
"dependencies": {
"#angular/core": "~10.1.3",
...
},
"devDependencies": {
"#angular/cli": "~10.1.3",
"#types/node": "^12.11.1",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.0.2",
...
}
}