【问题标题】:Nuxt.js throwing Failed to execute 'appendChild' on 'Node' on production due to Navbar由于 Navbar,Nuxt.js 抛出无法在 \'Node\' 上执行 \'appendChild\'
【发布时间】:2022-11-28 12:47:45
【问题描述】:

我有一个托管在 S3 存储桶上的 Nuxtjs 网站,它有多个路由/页面。我创建的这条特别的新路线正在投掷“无法在“节点”上执行“appendChild”:此节点类型不支持此方法。“(检查下面的 SS)

它不会在本地机器上发生,也不会抛出任何错误。显然,当我部署到 prod 时,应用程序会抛出如下 SS 所示的错误,但无法调试它,因为代码在构建时被缩小和丑化了。

奇怪的是,当我通过点击<NuxtLink>访问这条特定路线时,它工作正常,但如果我直接访问路线或者如果我点击<NuxtLink>并到达那里并重新加载页面,只有这样这个问题是不是来了。

您可以按照以下步骤重现此内容:

  1. 访问https://stackinvest.in
  2. 从导航栏点击“stack prime”,这将指向https://stackinvest.in/prime-mutual-funds
  3. 进入堆栈主页面后,重新加载页面。您将看到错误。

    直接访问https://stackinvest.in/prime-mutual-funds路由也会看到这个错误。

    问题是还有其他路线,正如您在 [主页] 的 Navbar 上看到的那样。如果我按照上述步骤操作,它们都工作正常。 (https://www.stackinvest.in/)

    我有一个默认布局文件(由 Nuxt CLI 本身创建)layouts/default.vue。代码如下所示:

    <template>
      <div ref="layout">
        <app-navbar :isMobile="isMobile" @getstack="showModal = true"/>
        <nuxt />
        <app-download v-if="showModal" :is-mobile="isMobile" @close="showModal=false"></app-download>
      </div>
    </template>
    <script>
    import Navbar from '../components/Navbar';
    import Download from '../components/Download';
    
    export default {
      name: 'Navbar',
      components: {
        appNavbar: Navbar,
        appDownload: Download
      },
      data() {
        return {
          showModal: false,
          size: Number
        };
      },
      computed: {
        isMobile() {
          return this.size < 768;
        }
      },
      mounted() {
        this.size = this.$refs.layout.clientWidth;
      }
    };
    </script>
    
    

    代码components/Navbar.vue。它不包含 .removeEventListener() 的任何代码。我确实尝试在 &lt;app-navbar&gt; 周围添加 &lt;client-only&gt;&lt;/client-only&gt; 包装标签 标签。什么都没用!

    <template>
      <nav class="nav-bar">
        <div class="d-flex justify-content-center px-0 px-sm-128">
          <div v-if="!isMobile" class="nav-bar-wrapper py-sm-4">
            <div class="brand-logo">
              <NuxtLink to="/">
                <img alt="Stack Finance logo" data-not-lazy height="35px"
                     src="https://webstatic.stackfinance.co/website/Group 73740.svg"
                >
              </NuxtLink>
            </div>
            <div class="d-flex justify-content-center">
              <div class="nav-links w-100 mr-sm-4">
                <div class="nav-links-item">
                  <NuxtLink class="fo-link" to="/prime-mutual-funds">
                    stack prime
                    <div class="designer-line"></div>
                  </NuxtLink>
                </div>
                <div class="nav-links-item">
                  <NuxtLink class="fo-link gt-nav-faq" to="/faq">
                    faq
                    <div class="designer-line"></div>
                  </NuxtLink>
                </div>
                <div class="nav-links-item">
                  <NuxtLink class="fo-link gt-nav-blog" to="/blogs">
                    blog
                    <div class="designer-line"></div>
                  </NuxtLink>
                </div>
              </div>
              <button id="nav-cta" ref="navCta" class="app-btn--sm" @click="focusSection">download stack</button>
            </div>
          </div>
          <div v-else class="nav-bar-wrapper--mob p-3">
            <div class="brand-logo">
              <NuxtLink to="/">
                <img alt="Stack Finance logo" data-not-lazy height="22px"
                     src="https://webstatic.stackfinance.co/website/Group 73740.svg"
                >
              </NuxtLink>
            </div>
            <div class="nav-links--collapsed">
              <div class="menu-icon">
                <input id="toggle" type="checkbox" @change="toggleMenu">
                <label for="toggle"></label>
              </div>
            </div>
            <div v-if="showMenu" class="menu-container">
              <div class="nav-links-item">
                <NuxtLink class="fo-link" to="/prime-mutual-funds">stack prime</NuxtLink>
              </div>
              <div class="nav-links-item gt-nav-faq">
                <NuxtLink class="fo-link" to="/faq">faq</NuxtLink>
              </div>
              <div class="nav-links-item">
                <NuxtLink class="fo-link gt-nav-blog" to="/blogs">blog</NuxtLink>
              </div>
            </div>
          </div>
        </div>
      </nav>
    </template>
    
    <script>
    export default {
      name: 'Navbar',
      props: {
        isMobile: Boolean
      },
      data() {
        return {
          showMenu: false
        };
      },
      methods: {
        focusSection() {
          this.$emit('getstack');
        },
        toggleMenu() {
          this.showMenu = !this.showMenu;
        }
      }
    };
    </script>
    
    <style lang="scss">
    ...
    </style>
    

    包.json

    {
      "name": "stack-web",
      "version": "4.0.0",
      "description": "",
      "author": "XYZ,
      "private": true,
      "scripts": {
        "dev:staging": "gulp set --env=staging && nuxt",
        "dev:prod": "gulp set --env=prod && nuxt",
        "build:staging": "gulp set --env=staging && nuxt build",
        "build:prod": "gulp set --env=prod && nuxt build",
        "start": "nuxt start",
        "generate": "nuxt generate",
        "lint": "eslint --ext .js,.vue --ignore-path .gitignore ."
      },
      "dependencies": {
        "@nuxtjs/axios": "^5.9.5",
        "@nuxtjs/dayjs": "^1.4.0",
        "@nuxtjs/dotenv": "^1.4.1",
        "@nuxtjs/gtm": "^2.4.0",
        "@nuxtjs/robots": "^2.5.0",
        "@nuxtjs/sitemap": "^2.4.0",
        "@tryghost/content-api": "^1.11.0",
        "bootstrap-vue": "^2.2.2",
        "glob-all": "^3.1.0",
        "nuxt": "^2.0.0",
        "nuxt-helmet": "^1.2.3",
        "nuxt-lazy-load": "^1.2.9",
        "purgecss-webpack-plugin": "^2.0.5",
        "tinyurl": "^1.1.7",
        "vue": "^2.7.14",
        "vue-gtag": "^1.16.1",
        "vue-loader": "^15.10.0",
        "vue-slick-carousel": "^1.0.6",
        "vue-social-sharing": "^3.0.9",
        "vuelidate": "^0.7.7"
      },
      "devDependencies": {
        "@nuxt/types": "^2.15.7",
        "@nuxtjs/eslint-config": "^1.0.1",
        "@nuxtjs/eslint-module": "^1.0.0",
        "@nuxtjs/stylelint-module": "^3.1.0",
        "babel-eslint": "^10.0.1",
        "eslint": "^6.1.0",
        "eslint-plugin-nuxt": ">=0.4.2",
        "gulp": "^4.0.2",
        "gulp-clean": "^0.4.0",
        "gulp-rename": "^2.0.0",
        "nuxt-compress": "^5.0.0",
        "sass": "^1.56.1",
        "sass-loader": "^10.1.1",
        "stylelint": "^10.1.0",
        "yargs": "^15.1.0"
      }
    }
    
    

    nuxt.config.js

    /* eslint-disable dot-notation */
    // import path from 'path';
    //
    // import PurgecssPlugin from 'purgecss-webpack-plugin';
    // import glob from 'glob-all';
    // import shrinkRay from 'shrink-ray-current';
    require('dotenv').config();
    
    const keywords = `keywords`;
    
    export default {
      mode: 'universal',
      server: {
        port: process.env.PORT || 5100,
        host: '0.0.0.0' // default: localhost
      },
      // render: {
      //   compressor: shrinkRay()
      // },
      router: {
        scrollBehavior() {
          let s;
          if (process.client) {
            s = { x: 0, y: 0 };
          }
          return s;
        }
      },
      /*
       ** Headers of the page
       */
      head: {
        ...
      },
      /*
       ** Customize the progress-bar color
       */
      loading: {
        color: '#fff'
      },
      /*
       ** Global CSS
       */
      css: [
        '@/assets/scss/app.scss'
      ],
      /*
       ** Plugins to load before mounting the App
       */
      plugins: [
        { src: '~/plugins/Vuelidate' },
        { src: '~/plugins/hotjar', mode: 'client' },
        { src: '~/plugins/gtag', mode: 'client' },
        { src: '~/utils', mode: 'client' },
        { src: '~/plugins/directives.js' }
      ],
      /*
       ** Nuxt.js dev-modules
       */
      buildModules: [
        'nuxt-compress'
        // // Doc: https://github.com/nuxt-community/eslint-module
        // '@nuxtjs/eslint-module',
        // // Doc: https://github.com/nuxt-community/stylelint-module
        // '@nuxtjs/stylelint-module'
      ],
      /*
       ** Nuxt.js modules
       */
      modules: [
        'bootstrap-vue/nuxt',
        '@nuxtjs/axios',
        '@nuxtjs/dotenv',
        '@nuxtjs/gtm',
        'nuxt-lazy-load',
        'nuxt-helmet',
        '@nuxtjs/robots',
        '@nuxtjs/sitemap',
        'vue-social-sharing/nuxt',
        '@nuxtjs/dayjs'
      ],
      robots: {
        ...
      },
      sitemap: {
        ...
      },
      gtm: {
        id: '<KEY>',
        pageTracking: true,
        pageViewEventName: 'nuxtRoute'
      },
      dayjs: {
        locales: ['en'],
        defaultLocale: 'en',
        plugins: [
          'utc' // import 'dayjs/plugin/utc'
        ]
      },
      module: {
        rules: [
          {
            test: /\.s[ac]ss$/i,
            use: ['style-loader', 'css-loader', 'sass-loader']
          }
        ]
      },
      bootstrapVue: {
        bootstrapCSS: false,
        bootstrapVueCSS: false
      },
      /*
       ** Build configuration
       */
      build: {
        /*
         ** You can extend webpack config here
         */
        extractCSS: true,
        extend(config, { isDev, isClient }) {
          if (isDev && isClient) {
            config.devtool = 'source-map';
            config.plugins.push(
              // new PurgecssPlugin({
              //   paths: glob.sync([
              //     path.join(__dirname, './pages/**/*.vue'),
              //     path.join(__dirname, './layouts/**/*.vue'),
              //     path.join(__dirname, './components/**/*.vue')
              //   ]),
              //   whitelist: ['html', 'body']
              // })
            );
          }
        }
      }
    };
    
    

    prime-mutual-funds.vue 的代码

    <template>
      <main ref="page" class="prime-main">
        <section class="hero">
          ...
        </section>
    
        <section class="risk-analysis">
          <div class="row">
            <div v-if="!isMobile" class="col-sm">
              ...
            </div>
            <div class="col-sm d-flex align-items-center mb-4 mb-sm-0">
              <div>
               
                <a v-if="isMobile" class="text-color text-decoration-done fw-600" :href="getOS() === 'android' ? 'URL1': 'url2'">
                  
                </a>
                <a v-else class="text-color text-decoration-done fw-600" @click="showModal = true" href="javascript:void(0)">
                  ...
                </a>
              </div>
            </div>
            <div v-if="isMobile" class="col-sm">
             ...
            </div>
          </div>
        </section>
    
        <section class="prime-detail">
          <div class="prime-detail-header">
            <h2 class="section-heading">Lorem Lorem</h2>
            <div class="d-flex mt-3 mt-sm-4">
              <a href="#options" class="prime-detail-tab" @click="activeTabIndex = 0" :class="{'active': activeTabIndex === 0}">investment options</a>
              <a href="#automate" class="prime-detail-tab" @click="activeTabIndex = 1" :class="{'active': activeTabIndex === 1}">automated investing</a>
              <a href="#track" class="prime-detail-tab" @click="activeTabIndex = 2" :class="{'active': activeTabIndex === 2}">actionable insights from experts</a>
            </div>
          </div>
    
          <section id="options" class="p-4" v-if="isMobile">
            ...
          </section>
          <section v-else class="scroller pb-sm-128">
            <div class="scroller-left">
              <div id="options" class="scrollable-element">
                <div id="1" class="mxw-600" v-intersection="{threshold: .7, cb: updateRightContainer}">
                 ...
                </div>
              </div>
              <div class="scrollable-element">
                <div id="2" class="mxw-600" v-intersection="{threshold: .7, cb: updateRightContainer}">
                 ...
                </div>
              </div>
              <div id="automate" class="scrollable-element">
                <div id="3" class="mxw-600" v-intersection="{threshold: .7, cb: updateRightContainer}">
                  ...
                </div>
              </div>
              <div class="scrollable-element">
                <div id="4" class="mxw-600" v-intersection="{threshold: .7, cb: updateRightContainer}">
                  ...
                </div>
              </div>
              <div class="scrollable-element">
                <div id="5" class="mxw-600" v-intersection="{threshold: .7, cb: updateRightContainer}">
                  ...
                </div>
              </div>
              <div id="track" class="scrollable-element">
                <div id="6" class="mxw-600" v-intersection="{threshold: .7, cb: updateRightContainer}">
                  ...
                </div>
              </div>
              <div class="scrollable-element">
                <div id="7" class="mxw-600" v-intersection="{threshold: .7, cb: updateRightContainer}">
                  ...
                </div>
              </div>
            </div>
            <div class="scroller-right">
              <div class="sticky-content">
                <div class="sticky-content-box" v-if="activateText">
                  <span class="sticky-content-tag" :style="{backgroundColor: contentIndex[activeContentIndex].bgc}">{{ contentIndex[activeContentIndex].tag }}</span>
                  <h3 class="sticky-content-title mt-4">
                    {{ contentIndex[activeContentIndex].title }}
                  </h3>
                </div>
                <img v-else height="520px" ref="info" alt="info-img" src="https://webstatic.stackfinance.co/website/goals.webp"/>
              </div>
            </div>
          </section>
        </section>
    
        <section class="rewards">
          <h3 v-if="isMobile" class="rewards-heading">good financial habits <br> deserve <span class="text-white">great</span> rewards</h3>
          <h3 v-else class="rewards-heading">good financial habits deserve <br> <span class="text-white">great</span> rewards</h3>
          <h5 class="rewards-subheading">choose a free US stock with every investment.</h5>
          <a :href="getOS() === 'android' ? '<url1>': '<url1>'"
             v-if="isMobile" class="app-btn--sm d-block bgi-none bg-white text-color mx-auto my-4" style="width: 190px;">
            download stack
          </a>
          <button v-else class="app-btn--white mt-sm-4" @click="showModal = true">download stack</button>
        </section>
    
        <app-grow-wealth :is-mobile="isMobile" @download="showModal = true"></app-grow-wealth>
        <app-footer :is-mobile="isMobile"></app-footer>
    
        <div v-if="isMobile" class="store-container py-2 px-4 bg-white position-fixed">
          <a href="https://stack.app.link/3yAcChs5emb" rel="noopener">
            <img height="40px" src="https://webstatic.stackfinance.co/website/playstore.png" alt="playstore">
          </a>
          <a href="https://stack.app.link/10JkMU6Xemb" rel="noopener">
            <img class="float-right" height="40px" src="https://webstatic.stackfinance.co/website/appstore.png" alt="appstore">
          </a>
        </div>
    
        <div v-if="showFaq" class="pop-box">
          <app-faq-section :squeeze="true"></app-faq-section>
        </div>
        <button id="popover-button-sync" class="floating-help" @click="showFaq = !showFaq">
          <img height="40px" src="https://webstatic.stackfinance.co/website/Chat 4_2x.png" alt="messenger">
        </button>
        <app-download v-if="showModal" :is-mobile="isMobile" @close="showModal=false"></app-download>
      </main>
    </template>
    
    <script>
    import Footer from '../components/Footer';
    import FaqSection from '../components/Faq-Section';
    import Download from '../components/Download';
    import { fetchOS } from '../utils';
    import GrowWealth from '../components/GrowWealth';
    
    export default {
      name: 'PrimeMutualFunds',
      components: {
        appFooter: Footer,
        appFaqSection: FaqSection,
        appDownload: Download,
        appGrowWealth: GrowWealth
      },
      data() {
        return {
          size: Number,
          showModal: false,
          showFaq: false,
          activateText: false,
          activeTabIndex: 0,
          imgIndex: {
            1: 'URL',
            2: 'URL',
            6: 'URL',
            7: 'URL'
          },
          contentIndex: {
            3: {
              tag: '...',
              bgc: '#f9f2ea',
              title: '...'
            },
            4: {
              tag: '...',
              bgc: '#e1f4f0',
              title: '...'
            },
            5: {
              tag: 'investment reminder',
              bgc: '#ccebff',
              title: '...'
            }
          },
          activeContentIndex: 3
        };
      },
      computed: {
        isMobile() {
          return this.size < 768;
        }
      },
      mounted() {
        this.size = this.$refs.page.clientWidth;
      },
      methods: {
        getOS: () => fetchOS(),
        updateRightContainer({ isIntersecting, target }) {
          if (isIntersecting) {
            const x = target.getAttribute('id');
            this.activeTabIndex = 0;
            const ids = [3, 4, 5];
            if (ids.includes(+x)) {
              this.activateText = true;
              this.activeContentIndex = x;
              this.activeTabIndex = 1;
              return;
            }
    
            if ([6, 7].includes(+x)) {
              this.activeTabIndex = 2;
            }
    
            this.activateText = false;
            if (this.$refs.info) {
              this.$refs.info.setAttribute('src', this.imgIndex[x]);
            }
          }
        }
      }
    };
    </script>
    
    <style lang="scss" scoped>
    ...
    </style>
    
    

    direction.js代码

    
    Vue.directive('intersection', {
      bind(el, binding) {
        const threshold = binding.value.threshold || 0;
        const defaultCb = () => {
          console.log('No callback passed');
        };
        const cb = binding.value.cb || defaultCb;
    
        if (isNaN(threshold)) {
          return;
        }
        const observer = new IntersectionObserver((entries) => {
          entries.forEach((elem) => {
            cb(elem);
          });
        }, { threshold });
        observer.observe(el);
      }
    });
    
    Vue.directive('scroll', {
      inserted(el, binding) {
        const f = function(evt) {
          if (binding.value(evt, el)) {
            window.removeEventListener('scroll', f);
          }
        };
        window.addEventListener('scroll', f);
      }
    });
    

    如果我注释掉导航栏并部署,重新加载工作正常,这个 URL 没有任何问题-https://stackinvest.in/prime-mutual-funds

【问题讨论】:

  • 正如你所说Once you're on stack prime page, reload the page. You will see the error为什么它在reload上询问Changes that you made may not be saved.
  • 您的代码可能不是同构的。一些解决方案是described here。在您的情况下,如果我们想更进一步,我们将需要您的package.json + nuxt.config.js 文件。此外,通过在本地构建(和预览)来调试它,您应该会遇到同样的问题。无需在生产中调试它。
  • 您可能还有一些中间件或全局插件。你有公共的 github 仓库吗?
  • 嗨@kissu,我已经添加了这两个文件。问题是没有控制台日志错误,并且在本地终端中。我确实有插件,但这也会影响其他路线。为什么只是这条特定的路线。
  • 可以添加prime-mutual-funds页面的代码吗?

标签: javascript vue.js nuxt.js


【解决方案1】:

Intersection Observerwindows 是仅客户端代码(不能在 Node.js 上下文中使用)。
您不能像现在这样使用它们,因为它们现在都在客户端和服务器端运行。

请参阅this answer 以了解解决该问题的方法(主要是仅在客户端运行您的代码),正如我在第一条评论中所建议的那样。

【讨论】:

    猜你喜欢
    • 2019-08-16
    • 1970-01-01
    • 1970-01-01
    • 2016-06-11
    • 2020-09-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-19
    相关资源
    最近更新 更多