nuxt-modules / i18n

I18n module for Nuxt
https://i18n.nuxtjs.org
MIT License
1.76k stars 485 forks source link

Nuxt dynamic routes defined in module, addRoutes ssr, 404 or Cannot read property 'layout' of undefined error or can't access property "scrollToTop", Page.options is undefined #1044

Closed onward-web closed 3 years ago

onward-web commented 3 years ago

Good time of the day,

I've encountered pretty strange issue with Nuxt@2.0 which is the following:

Create module and dynamically load routes Navigate to page using the sidebar - page works just fine (content displayed, etc) Copy page url and open it in a new tab - 404, BUT, all the meta data from the breadcrumbs generator is there (it parses the route name and creates entries for breadcrums) So, technically, nuxt is able to load the page, however, it fails to actually load it (strange wording, but i've no idea on how to describe it better).

Plugins used:

nuxt 2.14.12 nuxt-i18n - all routes are affixed with the locale string, so, for example: dashboarden, dashboardes, dashboardru, etc In order to dynamically load routes, the following plugin is used (defined under plugins in nuxt.config.js):

import Vue from 'vue'; export default ({ app: { router } }) => { const routingContext = require.context('../modules', true, /.routing.js$/);

routingContext.keys().map((file) => [file.replace(/(^.\/)|(\.js$)/g, ''), routingContext(file)]).reduce((pluginRoutes, [name, module]) => {
    Vue.use(module, router);
}, {});

}; The routing.js file inside the modules folder looks like this:

import { backendRoute, routeNameGenerator } from '~/libs/helpers'; import config from '~/config'; import forEach from 'lodash.foreach';

import CompanyPage from './pages/company';

export function install (Vue, router, options = {}) {

let routes = [
    {
        path: backendRoute('namespace/module/company'),
        name: routeNameGenerator('namespace.module.company'),
        component: CompanyPage
    }
];

let localizedRoutes = [];

forEach(routes, (route) => {
    forEach(config.application.localization.supportedLocales, (locale) => {
        let modifiedRoute = Object.assign({}, route);
        modifiedRoute.name = route.name + '___' + locale;
        localizedRoutes.push(modifiedRoute);
    });
});

router.addRoutes(localizedRoutes);

} And the helpers to generate correct route name and route path are the following:

/**

/**

I've checked the output for the this.$route from the page inside the /pages directory and dynamic page, it is the same (not the paths and names obviously), so at this point i have no idea what is wrong with my way of loading routes.

Any help is really appreciated, and thank you for your time!

P.S. I'm not using @nuxt\router in favor of automatic route generation for files inside the root\pages directory.

Update #1: Sometimes, it will occasionally throw the Cannot read property 'layout' of undefined error

rchl commented 3 years ago

Please prepare a minimal reproduction repo if you want me to look into your problem. Sorry but this report is a mess and all over the place. (Also, please mark your code blocks properly with a triple backtick character "```")

onward-web commented 3 years ago

Added the minimum set of code that is necessary to run and check the dynamic addition of routes, please see

My nuxt.config.js

module.exports = {
  ssr: true,
  mode: 'universal',
  srcDir: __dirname,
  loading: { color: '#007bff' },
    plugins: [
      {src: '~/plugins/route'}
    ],
 modules: [
    "@nuxtjs/axios",
    [
      'nuxt-i18n', {
      parsePages: false,
      pages: {
        contacts: {
          en: '/kont_test2',
          uk: '/kont_test3',
          ru: '/kont_test4',
      }},

      strategy:'prefix_except_default',
      detectBrowserLanguage: {
        useCookie: true,
        cookieKey: 'i18n_redirected',
        alwaysRedirect: true,
        fallbackLocale: 'en'
      },
      vueI18nLoader: true,
      locales: [
        {
          name: 'English',
          code: 'en',
          iso: 'en-US',
          file: 'en.js'
        },
        {
          name: 'Русский',
          code: 'ru',
          iso: 'ru-RU',
          file: 'ru.js'
        },
        {
          name: 'Українська',
          code: 'uk',
          iso: 'uk-UA',
          file: 'uk.js'
        },
      ],

      lazy: true,
      loadLanguagesAsync: true,
      silentTranslationWarn:true,
      langDir: 'locales/',
      defaultLocale: 'ru',

    },
    ],],
  hooks: {
    generate: {
      done (generator) {
        // Copy dist files to public/_nuxt
        if (generator.nuxt.options.dev === false && generator.nuxt.options.mode === 'spa') {
          const publicDir = join(generator.nuxt.options.rootDir, 'public', '_nuxt')
          removeSync(publicDir)
          copySync(join(generator.nuxt.options.generate.dir, '_nuxt'), publicDir)
          copySync(join(generator.nuxt.options.generate.dir, '200.html'), join(publicDir, 'index.html'))
          removeSync(generator.nuxt.options.generate.dir)
        }
      },

    }
  },
  build: {
    extend (config, { isDev, isClient }) {
      config.node = {
        fs: "empty"
      }
    }
  }

}

In the config I add plugin "route", plugins/route.js source code

function interopDefault(promise) {
  return promise.then(m => m.default || m);
}

export default ({app, router, routeres}) => {

  //axios.defaults.baseURL = process.env.apiUrl;
  // console.log(app.router);
  //afterEach

  app.router.beforeEach((to, from, next) => {

    const matched = app.router.getMatchedComponents(to);
    console.log("matched: " + matched.length);
    console.log("to.path: " + to.path);

    let dynamicView = () => interopDefault(import('~pages/post.vue'));

    if (!matched.length) {
      console.log('not-exist');

      app.router.addRoutes([
        {
          path: to.path,
          component: dynamicView,    
        }], { override: true });

      next(to.path);

    } else {
      console.log('route exist');
      next()     
    }

  })
}

pages/post.vue

<template>
    <div class = "test">test post page for this path</div>
</template>

<script>
    export default {

    }
</script>

Now the code is quite simplified, when checking if (! Matched.length) we add a simple march page "pages / post.vue" and should display the simple text "test post page for this path" For example, by contacting any url that does not exist, the route should be added. example.com/other_url From time to time I see errors: Cannot read property 'scrollToTop' of undefined (for now) Cannot read property 'layout' of undefined error (periodically) 404 page not found (periodically)

rchl commented 3 years ago

Sorry but I'm not gonna spend time combining an app from those incomplete blocks that you are providing.

Please create a reproduction on codesandbox for example. I've already started building it at https://codesandbox.io/s/nuxt-nuxt-i18n-issue-1044-ott6x so you can fork and complete it.

onward-web commented 3 years ago

Thank you. I added parts of the missing code. Link to "codesandbox" https://codesandbox.io/s/nuxt-nuxt-i18n-issue-1044-forked-t33du

At the moment, when you enter any test url, for example "https://0l1g0.sse.codesandbox.io/test_url", you get the error "Cannot read property 'scrollToTop' of undefined"

But I see it works on the latest version of nuxt, if also add a custom "scrollBehavior" to the "nuxt.config.js" configuration,

router: {
     scrollBehavior: function (to, from, savedPosition) {
       return savedPosition
     },

then the error "scrollToTop" disappears. That is, everything is fine. On previous versions of nuxt 2.12 there was a problem (404 or or Cannot read property 'layout' ), but now I see no problem. But I'm new to nuxt, don't quite understand if I'm overriding the scrollBehavior method correctly, it kinda should be like scroll up. Or perhaps the error lies deeper, and I am doing something wrong # #

rchl commented 3 years ago

This problem doesn't look nuxt-i18n related. If you disable this module you'll have the same issue.

I'm not sure what's the exact problem but it looks like this import is not resolved properly on the client-side:

let dynamicView = () => interopDefault(import("~/pages/post.vue"));