SSR Hydration Vue js Nuxt

时间:2021-02-19 09:26:28

标签: firebase vue.js nuxt.js server-side-rendering hydration

我已经被 Vue.js 的补水问题困住了 2 天多,似乎找不到解决方案。

上下文:

我有一个页面,我想在其中呈现服务器端的一些文章,以便将它们包含在我的源代码中以获得更好的 SEO。这些文章从我的 Firestore 加载,然后发送到要显示的子组件。

这是我的页面组件:

index.js(更新)

<template>
  <div>
    <AppHeader class="fixed-header"></AppHeader>

    <section id="intro" class="px-md">
      <div class="intro has-text-white p-none mt-lg py-lg">
        <div class="is-hidden-touch pt-lg"></div>
        <div class="columns is-mobile is-centered mb-none p-none">
          <div class="intro-content column pt-lg is-10-mobile is-8-tablet is-6-desktop
            max-content-width has-text-centered">

            <h1 class="titre main-title is-size-4-mobile is-size-3-tablet is-size-2-desktop
            has-text-weight-semibold">
              Première plateforme de transmission de startups &amp; TPE
            </h1>
            <p class="main-subtitle has-text-weight-medium mt-md">
              Nous aidons les entrepreneurs souhaitant arrêter leur activité
              à revendre leur travail, innovation ou savoir-faire.
            </p>

            <div class="ctas columns my-lg">
              <div class="column has-text-right has-text-centered-mobile"
                   data-aos="zoom-in" data-aos-delay="100" data-aos-once="true" data-aos-anchor="intro">
                <nuxt-link to="/valorisation">
                  <b-button id="cta-seller"
                            class="button zoom is-primary is-rounded has-text-weight-semibold">
                    Estimer gratuitement
                  </b-button>
                </nuxt-link>
              </div>
              <div class="column has-text-left has-text-centered-mobile"
                   data-aos="zoom-in" data-aos-delay="200" data-aos-once="true" data-aos-anchor="intro">
                <nuxt-link to="/annonces">
                  <b-button id="cta-buyer"
                            class="button zoom is-secondary is-rounded has-text-weight-semibold">
                    Découvrir nos annonces
                  </b-button>
                </nuxt-link>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <div class="is-hidden-tablet has-text-centered" @click="scrollBelowIntro">
      <b-icon icon="chevron-down" type="is-dark" size="is-large"></b-icon>
    </div>

    <section id="storybee" class="columns is-centered is-mobile my-lg">
      <div class="column is-10 max-content-width" data-aos="fade-up" data-aos-once="true" data-aos-delay="400">
        <h2 class="title is-size-3" @click="show = !show">Storybee vous permet</h2>

        <div class="columns is-mobile is-centered my-lg">
          <div class="column is-narrow m-none p-none">
            <div class="columns my-md">
              <div class="column is-narrow has-text-centered">
                <b-icon icon="lightbulb-on-outline" type="is-tertiary" size="is-large"></b-icon>
              </div>
              <div class="column">
                <h3 class="title is-size-5 has-text-weight-bold mb-sm">
                  De reprendre ou revendre une entreprise
                </h3>
                <p>
                  Via des <span class="has-text-weight-semibold">outils digitaux</span> simples, rapides &amp; transparents
                </p>
              </div>
            </div>
            <div class="columns my-md">
              <div class="column is-narrow has-text-centered">
                <b-icon icon="check-all" type="is-tertiary" size="is-large"></b-icon>
              </div>
              <div class="column">
                <h3 class="title is-size-5 has-text-weight-bold mb-sm">
                  D’être accompagné de A à Z
                </h3>
                <p>
                  Aucune prise de tête juridique ou financière,
                  <span class="has-text-weight-semibold">notre équipe est là pour vous !</span>
                </p>
              </div>
            </div>
            <div class="columns my-md">
              <div class="column is-narrow has-text-centered">
                <b-icon icon="arrow-right-box" type="is-tertiary" size="is-large"></b-icon>
              </div>
              <div class="column">
                <h3 class="title is-size-5 has-text-weight-bold mb-sm">
                  D’accèder à des opportunités uniques
                </h3>
                <p>
                  Avec une base d’annonces <span class="has-text-weight-semibold">vérifiées</span>
                  &amp; des repreneurs <span class="has-text-weight-semibold">qualifiés</span>
                </p>
              </div>
            </div>
            <div class="columns my-md">
              <div class="column is-narrow has-text-centered">
                <b-icon icon="flash" type="is-tertiary" size="is-large"></b-icon>
              </div>
              <div class="column">
                <h3 class="title is-size-5 has-text-weight-bold mb-sm">
                  De bénéficier d’une méthode simplifiée
                </h3>
                <p>
                  Vous choisissez ce que vous voulez racheter,
                  pour une transmission <span class="has-text-weight-semibold">simple &amp; efficace</span>
                </p>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>

    <section id="latest-sales" class="page-content-fullwidth rel has-background-light pb-none">

      <div class="columns is-mobile is-centered">
        <div class="column is-10 max-content-width">
          <h2 class="title is-size-3 pt-lg m-none">Nos dernières annonces</h2>
          <LatestSales></LatestSales>
        </div>
      </div>

      <div class="columns is-mobile pb-lg">
        <div class="column has-text-centered">
          <nuxt-link to="/annonces">
            <span data-aos="zoom-in" data-aos-delay="200" data-aos-once="true"
                  data-aos-anchor="#latest-sales">
              <b-button class="zoom has-text-weight-semibold" icon-right="chevron-right" rounded
                        type="is-secondary">
                Découvrir toutes les annonces
              </b-button>
            </span>
          </nuxt-link>
        </div>
      </div>
    </section>

    <section class="columns is-centered mt-xl">
      <div class="column is-10 max-content-width">
        <h2 class="title is-size-3  ml-lg">Les atouts de la transmission</h2>

        <div class="sell columns is-centered">
          <div class="column is-hidden-tablet m-none p-none mt-lg">
            <div class="columns is-mobile">
              <div class="column is-6 is-offset-3 p-none">
                <img class="illustration petal top-left" src="~assets/images/illustrations/transmit.jpg" loading="lazy" width="354" height="354" />
              </div>
            </div>
          </div>

          <div class="column">
              <!-- <div class="bubble bubble-1">
                  <img src="~assets/images/storybee/petal-storybee-coral.svg"/>
              </div>
              <div class="bubble bubble-2">
                  <img src="~assets/images/storybee/petal-storybee-coral.svg"/>
              </div>
              <div class="bubble bubble-3">
                  <img src="~assets/images/storybee/petal-storybee-coral.svg"/>
              </div>
              <div class="bubble bubble-4">
                  <img src="~assets/images/storybee/petal-storybee-coral.svg"/>
              </div> -->

              <div id="transmit" class="content columns is-centered mt-lg">
                <div class="column is-5 pr-none pt-xl mt-lg">
                  <div class="title-sell title-left p-sm pl-md">
                    <h2 class="has-text-white is-pulled-left ml-md">Je transmets...</h2>
                  </div>
                  <div style="clear: both"></div>

                  <div class="has-text-right m-lg">
                    <div data-aos="fade-in-left" data-aos-delay="300" data-aos-once="true" class="mb-md">
                      <h3 class="is-size-5 has-text-dark is-uppercase">
                        <b-icon type="is-primary" icon="chevron-right-circle"></b-icon>
                        Ce pour quoi j’ai travaillé dur
                      </h3>
                      <p>J’ai créé, développé un savoir-faire ou innové et transmets
                        l’ensemble de mon travail (marque, produits, services, site internet etc.)</p>
                    </div>

                    <div data-aos="fade-in-left" data-aos-delay="300" data-aos-once="true" class="mb-md">
                      <h3 class="is-size-5 has-text-dark is-uppercase">
                        <b-icon type="is-primary" icon="chevron-right-circle"></b-icon>
                        Quel que soit mon statut
                      </h3>
                      <p>Que j’aie une micro-entreprise ou TPE, qu’elle ait eu le temps d’avoir du succès ou non !</p>
                    </div>

                    <div data-aos="fade-in-left" data-aos-delay="300" data-aos-once="true" class="mb-md">
                      <h3 class="is-size-5 has-text-dark is-uppercase">
                        <b-icon type="is-primary" icon="chevron-right-circle"></b-icon>
                        Pour valoriser mon travail
                      </h3>
                      <p>Je souhaite récupérer mon investissement, faire en sorte que mon
                        travail ne se perde pas &amp; valoriser mon expérience</p>
                    </div>

                    <nuxt-link to="pages/transmettre">
                      <span data-aos="zoom-in" data-aos-delay="300" data-aos-once="true">
                        <b-button type="is-primary" rounded class="zoom has-text-weight-semibold m-sm"
                                  icon-right="chevron-right">
                          En savoir plus
                        </b-button>
                      </span>
                    </nuxt-link>
                  </div>
                </div>

                <div class="column is-hidden-mobile is-5 pr-none pt-xl m-none mt-lg">
                  <div class="title-sell title-right p-sm pl-md"></div>
                  <img class="illustration petal top-left" src="~assets/images/illustrations/transmit.jpg" loading="lazy" />
                </div>
              </div>
          </div>
        </div>
      </div>
    </section>

    <section class="columns is-centered mb-xl">
      <div class="column is-10 max-content-width">

        <div class="buy columns is-centered">

          <div class="column is-hidden-tablet m-none p-none mt-xl">
            <div class="columns is-mobile">
              <div class="column is-6 is-offset-3 p-none">
                <img class="illustration petal bottom-right" src="~assets/images/illustrations/continue.jpg" loading="lazy" width="354" height="354" />
              </div>
            </div>
          </div>

          <div class="column">
              <!-- <div class="bubble bubble-1">
                  <img src="~assets/images/storybee/petal-storybee-turquoise.svg"/>
              </div>
              <div class="bubble bubble-2">
                  <img src="~assets/images/storybee/petal-storybee-turquoise.svg"/>
              </div>
              <div class="bubble bubble-3">
                  <img src="~assets/images/storybee/petal-storybee-turquoise.svg"/>
              </div>
              <div class="bubble bubble-4">
                  <img src="~assets/images/storybee/petal-storybee-turquoise.svg"/>
              </div> -->

              <div id="continue" class="content columns is-centered mt-lg">
                <div class="column is-hidden-mobile is-5 pr-none pt-xl m-none mt-lg">
                  <div class="title-buy title-left p-sm pl-md"></div>
                  <img class="illustration petal bottom-right" src="~assets/images/illustrations/continue.jpg" loading="lazy" />
                </div>
                <div class="column is-5 pl-none pt-xl mt-lg">
                  <div class="title-buy title-right p-sm pl-md">
                    <h2 class="has-text-white is-pulled-right mr-md">Je reprends</h2>
                  </div>

                  <div class="m-lg">
                    <div data-aos="fade-in-left" data-aos-delay="300" data-aos-once="true" class="mb-md">
                      <h3 class="is-size-5 has-text-dark is-uppercase">
                        <b-icon type="is-success" icon="chevron-right-circle"></b-icon>
                        Pour me lancer ou me diversifier
                      </h3>
                      <p>En rachetant, je capitalise sur l’existant, diminue mon risque &amp; maximise
                        mes chances de réussite</p>
                    </div>

                    <div data-aos="fade-in-left" data-aos-delay="300" data-aos-once="true" class="mb-md">
                      <h3 class="is-size-5 has-text-dark is-uppercase">
                        <b-icon type="is-success" icon="chevron-right-circle"></b-icon>
                        Pour gagner du temps
                      </h3>
                      <p>Je gagne des compétences complémentaires, de l’expérience, une belle
                        histoire et le plus précieux pour réussir... du temps ! </p>
                    </div>

                    <div data-aos="fade-in-left" data-aos-delay="300" data-aos-once="true" class="mb-md">
                      <h3 class="is-size-5 has-text-dark is-uppercase">
                        <b-icon type="is-success" icon="chevron-right-circle"></b-icon>
                        Pour faire des économies
                      </h3>
                      <p>Créer ou innover peut coûter très (très) cher. Vous gérez votre budget selon le stade de
                        développement de l’entreprise, la reprise n’a jamais été aussi accessible !</p>
                    </div>

                    <nuxt-link to="/pages/reprendre">
                      <span data-aos="zoom-in" data-aos-delay="300" data-aos-once="true">
                        <b-button type="is-secondary" rounded class="zoom has-text-weight-semibold m-sm"
                                  icon-right="chevron-right">
                          En savoir plus
                        </b-button>
                      </span>
                    </nuxt-link>
                  </div>
                </div>
              </div>
          </div>
        </div>

      </div>
    </section>

    <section id="partners" class="partners columns is-centered has-background-light pt-xl pb-lg">
      <div class="column is-10 max-content-width" data-aos="fade-up" data-aos-delay="200" data-aos-once="true">
        <div class="columns is-mobile">
          <div class="title-container column is-narrow has-background-white px-lg py-md">
            <h2 class="titre is-size-3 has-text-dark has-text-weight-semibold has-background-white">
              Ils nous soutiennent
            </h2>
          </div>
        </div>

        <div class="level">
          <div class="level-item my-lg">
            <a href="https://lafrenchtech.com/" target="_blank">
              <img class="partner-logo" src="~assets/images/logos/logo-french-tech.png" width="215" height="128" loading="lazy" />
            </a>
          </div>
          <div class="level-item my-lg">
            <a href="https://www.techforgoodfr.org/" target="_blank">
              <img class="partner-logo" src="~assets/images/logos/logo-techforgood.png" width="200" height="155" loading="lazy"/>
            </a>
          </div>
          <div class="level-item my-lg">
            <a href="https://www.bpifrance.fr/" target="_blank">
              <img class="partner-logo" src="~assets/images/logos/logo-bpifrance.png" width="300" height="80" loading="lazy"/>
            </a>
          </div>
          <div class="level-item my-lg">
            <a href="http://www.poleactionmedia.com/" target="_blank">
              <img class="partner-logo" src="~assets/images/logos/logo-pole-action-media.png" width="250" height="134" loading="lazy"/>
            </a>
          </div>
          <div class="level-item my-lg">
            <a href="https://impactfrance.eco/" target="_blank">
              <img class="partner-logo" src="~assets/images/logos/logo-impact-france.png" width="340" height="148" loading="lazy"/>
            </a>
          </div>
        </div>
      </div>
    </section>

    <section id="products" class="products columns is-centered has-background-light pb-lg">
      <div class="column max-content-width px-lg">

        <div class="columns is-mobile mb-xl">
          <div class="title-container column is-narrow has-background-white px-lg py-md">
            <h2 class="titre title is-size-3 has-text-dark has-text-weight-semibold has-background-white">
              Nos outils
            </h2>
          </div>
        </div>

        <div class="columns is-centered is-mobile" data-aos="fade-up" data-aos-once="true">
          <div class="column is-10 toolcard py-none">
            <ToolCard button-color="is-primary"
                      button-link="/valorisation"
                      button-text="Estimer"
                      image="outils"
                      title="Estimez la valeur de votre petite ou micro entreprise"
                      class="my-lg">
            </ToolCard>
          </div>
        </div>
        <div class="columns is-centered is-mobile" data-aos="fade-up" data-aos-once="true">
          <div class="column is-10 toolcard py-none">
            <ToolCard button-color="is-secondary"
                      button-link="/buyer-search"
                      button-text="Activer"
                      image="annonces"
                      title="Activ’search - Ma veille active"
                      class="my-lg">
            </ToolCard>
            <!-- <ToolCard button-color="is-secondary"
              button-link="/activ-search"
              button-text="Activer ma veille"
              image="annonces"
              title="Activ’search - Ma veille active"
              class="my-lg">
            </ToolCard> -->
          </div>
        </div>
        <div class="columns is-centered is-mobile" data-aos="fade-up" data-aos-once="true">
          <div class="column is-10 toolcard py-none">
            <ToolCard button-color="is-primary"
                      button-link="/articles/guide-transmission"
                      button-text="Lire les articles"
                      image="cedant"
                      title="Le guide de la transmission"
                      class="my-lg">
            </ToolCard>
          </div>
        </div>
        <div class="columns is-centered is-mobile" data-aos="fade-up" data-aos-once="true">
          <div class="column is-10 toolcard py-none">
            <ToolCard button-color="is-secondary"
                      button-link="/articles/guide-transmission"
                      button-text="Lire le guide"
                      image="repreneur"
                      title="Le guide de la reprise"
                      class="my-lg">
            </ToolCard>
          </div>
        </div>
      </div>
    </section>

    <section id="articles" class="articles mt-lg"
             data-aos="fade-up" data-aos-delay="400" data-aos-anchor="bottom-center" data-aos-once="true">
      <div class="background"></div>
      <div class="columns is-centered is-mobile mt-none">
        <div class="column is-10-desktop is-12-touch max-content-width pt-none">
          <h2 class="titre is-size-3 has-text-weight-semibold has-text-dark my-md"
              data-aos="fade-up" data-aos-delay="100" data-aos-once="true">
            Nos derniers blogs
          </h2>

          <div  class="level">
            <div v-for="(article, articleIndex) in articles" :key="articleIndex" class="level-item mx-sm">
              <ArticleCard @error="setArticleError" :article="article" class="mt-lg">
              </ArticleCard>
            </div>
          </div>
        </div>
      </div>
    </section>

    <div class="fixed-contact zoom"
         data-aos="zoom-in" data-aos-once="true" data-aos-delay="500" data-aos-anchor="#intro">
      <!-- <img src="~assets/images/icons/question-mark.svg" /> -->
      <b-button type="is-primary" icon-left="phone" rounded class="zoom"
                @click="isContactModalActive = true">
        Nous contacter
      </b-button>
    </div>

    <b-modal v-model="isContactModalActive"
             :can-cancel="['escape', 'outside']"
             :full-screen="$device.isMobile">
      <div class="sb-modal has-rounded-corners p-lg">
        <div class="is-pulled-right is-clickable" @click="isContactModalActive = false">
          <b-icon icon="close"></b-icon>
        </div>
        <div>
          <h4 class="is-size-5 has-text-weight-semibold mb-md">Besoin de renseignements ?</h4>
          <div class="sb-modal-content">
            <p>
              Vous pouvez nous contacter au
              <a href="tel:0698497479" class="has-text-weight-semibold">06 98 49 74 79</a>,
              ou entrer votre nom et numéro ci-dessous pour qu'on vous rappelle :
            </p>
            <CallForm :context="{
              origin: 'Home Page: boutton contact'
            }">
            </CallForm>
          </div>
        </div>
      </div>
    </b-modal>

    <Newsletter></Newsletter>

    <AppFooter @show-contact="isContactModalActive = true"></AppFooter>
  </div>
</template>

<script>
import { mapState, mapGetters } from 'vuex'
import _map from 'lodash/map'
// import { KinesisContainer, KinesisElement} from 'vue-kinesis'
import AppHeader from '@/components/menus/AppHeader'
import AppFooter from '@/components/menus/AppFooter'
import ArticleCard from '@/components/articles/ArticleCard'
import LatestSales from '@/components/sales/LatestSales'
import Newsletter from '@/components/Newsletter'
import ToolCard from '@/components/ToolCard'
import CallForm from '@/components/forms/CallForm'
import {articleConverter} from "data-model";

export default {
  layout: (ctx) => ctx.isMobile ? 'bare-mobile' : 'bare',
  components: {
    AppHeader,
    AppFooter,
    Newsletter,
    ArticleCard,
    LatestSales,
    ToolCard,
    // KinesisContainer,
    // KinesisElement
    CallForm,
  },
  async fetch() {
    console.log("FETCHHHHHH");
    await this.loadArticles();
  },
  data() {
    return {
      isLoading: true,
      focusedArticleIndex: 0,
      isContactModalActive: false,
      articleError: false,
      articles: null,
      formData: {
        email: null
      }
    }
  },
  computed: {
    ...mapState({
      loggedInUser: (state) => state.loggedInUser.user,
      authUser: (state) => state.auth.user
    }),
    ...mapGetters({
      isAuthenticated: 'auth/isAuthenticated',
      hasLoggedInUser: 'loggedInUser/hasLoggedInUser'
    }),
  },
  async mounted() {
    if (this.$route.query.section) {
      this.$scrollTo(`#${this.$route.query.section}`, 300, { offset: -100 })
    }
  },
  methods: {
    scrollBelowIntro() {
      this.$scrollTo('#storybee', 300, { offset: -100 });
    },
    setArticleError() {
      this.articleError = true;
    },
    subscribe() {
      this.$fire.functions.httpsCallable('mailchimp-subscribeToNewsletter')({
        email: this.formData.email
      })
        .then(() => {
          // console.log('SUCCESS', result)
          this.$buefy.toast.open({
            duration: 5000,
            message: 'On vous a bien inscrit ! On vous tient au courant très vite ? Merci !',
            position: 'is-bottom',
            type: 'is-success'
          })
        })
        .catch((err) => {
          console.error('ERROR', err.code, err.message, err.details)
          const code = err.code
          let errorMessage = 'Une erreur est survenue. Ré-essayez plus tard ou contactez-nous à <strong>hello@storybee.fr</strong>'
          if (code === 'invalid-argument')
            errorMessage = err.message
          this.$buefy.toast.open({
            duration: 5000,
            message: errorMessage,
            position: 'is-bottom',
            type: 'is-danger'
          })
        })
    },
    async loadArticles() {
      console.log("inside loadArticles");
      const articles = [];
      try {
        this.isLoading = true;
        const querySnapshot = await this.$fire.firestore.collection("blogPosts")
          // .where("publishedDate", "<", new Date())
          .limit(3)
          .get();
        // console.log("RESULT", querySnapshot);
        querySnapshot.forEach((articleDoc) => {
          const article = articleConverter.fromFirestoreData(articleDoc.data());
          articles.push(article);
        });
        console.log("articles IDs", _map(articles, (article) => article.articleId));
        this.articles = articles;
        this.isLoading = false;
      }
      catch(err) {
        console.error("ERROR", err);
        this.articleError = true;
      }
    }
  },
}
</script>

<style lang="sass" scoped>
// @import '~assets/styles/intro-rosaces.sass'
@import '~assets/styles/modal.sass'
@import '~assets/styles/landing.sass'

.fixed-contact
  position: fixed
  bottom: 3vh
  right: 2vw

  .button
    box-shadow: 0px 2px 4px $gs3
</style>

This is were I call my loadArticles function:

  async fetch() {
    console.log("FETCHHHHHH");
    await this.loadArticles();
  },

这是 loadArticlesFunction:

async loadArticles() {
      console.log("hehehe");
      try {
        this.isLoading = true;
        this.$fire.firestore.collection("blogPosts")
          .where("publishedDate", "<", new Date())
          .get()
          .then((querySnapshot) => {
            const articles = [];
            querySnapshot.forEach((articleDoc) => {
              const article = articleConverter.fromFirestoreData(articleDoc.data());
              articles.push(article);
            });
            console.log("this are the articles", articles);
            this.articles = articles;
            this.isLoading = false;
          })
          .catch((error) => {
            this.articleError = true;
          });
      } catch(err) {
        this.articleError = true;
      }

如文档中所述,我已将 v-for 封装为仅限客户端。

为了调试,我设置了各种断点,以便能够从 hydr 函数读取 elm 对​​象,并尝试在此 html 对象中找到错误消息,但没有成功。我还尝试使用 asyncData 而不是 fetch 但无法访问它。我最近的测试是使用 promise 强制 Nuxt 等待获取完成,但也没有运气。

我认为问题是mounted是在我的fetch结束之前执行的,从而导致了这个错误。

编辑

如果我在 catch 中添加一个 console.log,我会得到这个:

ERROR Error [FirebaseError]: Function Query.where() called with invalid data. Unsupported field value: a custom Date object

所以如果我注释掉这一行:

.where("publishedDate", "<", new Date())

一切正常。

但现在我不知道为什么 new Date 在这里不起作用......以及用什么替换它......

1 个答案:

答案 0 :(得分:0)

我通过将 Date 对象转换为 Firebase 时间戳解决了这个问题,如下所示:

const now = this.$fireModule.firestore.Timestamp.fromDate(new Date());
        const querySnapshot = await this.$fire.firestore.collection("blogPosts")
          .where("publishedDate", "<", now)
          .orderBy("publishedDate", "desc")
          .limit(3)
          .get();

但是,我不明白为什么这会导致问题以及为什么 Date 可以在服务器端使用。如果有人能分享一些关于这方面的信息,那就太好了!