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

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.

Related

webpack.config + nuxt.config,js

I am new to nuxt.
I want to add following webpack config (from docs for ckeditor vue) to nuxt.js.config
https://ckeditor.com/docs/ckeditor5/latest/installation/getting-started/frameworks/vuejs-v2.html
const path = require( 'path' );
const CKEditorWebpackPlugin = require( '#ckeditor/ckeditor5-dev-webpack-plugin' );
const { styles } = require( '#ckeditor/ckeditor5-dev-utils' );
module.exports = {
// The source of CKEditor is encapsulated in ES6 modules. By default, the code
// from the node_modules directory is not transpiled, so you must explicitly tell
// the CLI tools to transpile JavaScript files in all ckeditor5-* modules.
transpileDependencies: [
/ckeditor5-[^/\\]+[/\\]src[/\\].+\.js$/,
],
configureWebpack: {
plugins: [
// CKEditor needs its own plugin to be built using webpack.
new CKEditorWebpackPlugin( {
// See https://ckeditor.com/docs/ckeditor5/latest/features/ui-language.html
language: 'en',
// Append translations to the file matching the `app` name.
translationsOutputFile: /app/
} )
]
},
// Vue CLI would normally use its own loader to load .svg and .css files, however:
// 1. The icons used by CKEditor must be loaded using raw-loader,
// 2. The CSS used by CKEditor must be transpiled using PostCSS to load properly.
chainWebpack: config => {
// (1.) To handle editor icons, get the default rule for *.svg files first:
const svgRule = config.module.rule( 'svg' );
// Then you can either:
//
// * clear all loaders for existing 'svg' rule:
//
// svgRule.uses.clear();
//
// * or exclude ckeditor directory from node_modules:
svgRule.exclude.add( path.join( __dirname, 'node_modules', '#ckeditor' ) );
// Add an entry for *.svg files belonging to CKEditor. You can either:
//
// * modify the existing 'svg' rule:
//
// svgRule.use( 'raw-loader' ).loader( 'raw-loader' );
//
// * or add a new one:
config.module
.rule( 'cke-svg' )
.test( /ckeditor5-[^/\\]+[/\\]theme[/\\]icons[/\\][^/\\]+\.svg$/ )
.use( 'raw-loader' )
.loader( 'raw-loader' );
// (2.) Transpile the .css files imported by the editor using PostCSS.
// Make sure only the CSS belonging to ckeditor5-* packages is processed this way.
config.module
.rule( 'cke-css' )
.test( /ckeditor5-[^/\\]+[/\\].+\.css$/ )
.use( 'postcss-loader' )
.loader( 'postcss-loader' )
.tap( () => {
return {
postcssOptions: styles.getPostCssConfig( {
themeImporter: {
themePath: require.resolve( '#ckeditor/ckeditor5-theme-lark' )
},
minify: true
} )
};
} );
}
};
My nuxt config is simply current, i did add some modules like axios,boostrap vue and auth to it.
export default {
// Disable server-side rendering: https://go.nuxtjs.dev/ssr-mode
ssr: false,
// Target: https://go.nuxtjs.dev/config-target
target: "static",
// Global page headers: https://go.nuxtjs.dev/config-head
head: {
title: "spa",
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" }],
},
// Global CSS: https://go.nuxtjs.dev/config-css
css: ["~/assets/less/colors.less"],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
plugins: [],
// Auto import components: https://go.nuxtjs.dev/config-components
components: true,
// Modules for dev and build (recommended): https://go.nuxtjs.dev/config-modules
buildModules: [],
// Modules: https://go.nuxtjs.dev/config-modules
modules: [
// https://go.nuxtjs.dev/bootstrap
"bootstrap-vue/nuxt",
"#nuxtjs/axios",
"#nuxtjs/auth-next",
],
auth: {
redirect: {
login: "/login",
logout: "/login",
callback: false,
home: "/",
},
strategies: {
laravelSanctum: {
provider: "laravel/sanctum",
url: "http://localhost",
user: {
property: false, // <--- Default "user"
autoFetch: true,
},
// endpoints: {
// login: { url: "api/login", method: "post" },
// logout: { url: "api/auth/logout", method: "post" },
// user: { url: "api/user", method: "get" },
// },
},
},
},
axios: {
credentials: true,
baseURL: "http://localhost", // Used as fallback if no runtime config is provided
// withCredentials: true,
headers: {
accept: "application/json",
common: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET,PUT,POST,DELETE,PATCH,OPTIONS",
"Access-Control-Allow-Credentials": true,
},
delete: {},
get: {},
head: {},
post: {},
put: {},
patch: {},
},
},
// Build Configuration: https://go.nuxtjs.dev/config-build
build: {},
router: {
middleware: ["auth"],
},
};
And if config like that can be combine with webpack config for ckeditor , is that right way to do so ?
Or should i separate this and build in another direcrory with another sepparate webpack config?
I guess you're looking for that one: https://nuxtjs.org/docs/configuration-glossary/configuration-build#extend
Could create an external config and import it into this object or directly do that inline as most people do.
There are quite some answers on Stackoverflow anyway on how to achieve specific parts of the configuration, feel free to give it a search to find out what you need.

<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

Vue How to fix 404 error when using nuxt proxy and nuxt axios

In my vue-app I'm using the #nuxtjs/proxy together with #nuxtjs/axios but for some reason I don't know, I always get an Request failed with status code 404 -error when calling the api.
Here is my nuxt.config.js
modules: [
'#nuxtjs/axios',
'#nuxtjs/proxy'
],
axios: {
proxy: true,
},
proxy: {
"/api": {
target: "https://url-to-api.com/api",
pathRewrite: { "^/api/": "" },
}
},
then in my vue-component
created(){
const { data } = await this.$axios.get(
`/api/products/all`
)
...
}
when I check on the network tab, the request is:
https://mypage.com/api/products/all
that request returns like mentioned above Request failed with status code 404
What am I doing wrong?
nuxt version: 2.15.8
Try this:
// nuxt.config.js
axios: {
baseURL: '/',
browserBaseURL: '/',
proxy: true,
},
proxy: {
'/api/': {
target: process.env.BASE_URL,
pathRewrite: {'^/api/': ''},
},
},
publicRuntimeConfig: {
axios: {
browserBaseURL: process.env.BROWSER_BASE_URL,
},
},
privateRuntimeConfig: {
axios: {
baseURL: process.env.BASE_URL,
},
},
and in your component:
created(){
const { data } = await this.$axios.get(
`products/all`
);
}
in .env:
BROWSER_BASE_URL=/api/
BASE_URL=https://url-to-api.com/api/
it works for me.

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!

Nuxt huge memory usage / leakage and how to prevent

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.