nuxt / content

The file-based CMS for your Nuxt application, powered by Markdown and Vue components.
https://content.nuxt.com
MIT License
3.08k stars 624 forks source link

Initial Load Taking Too Long #513

Open jhull opened 3 years ago

jhull commented 3 years ago

This may be a result of having too much content (1200 pages), but on the initial load of "The Latest" content - it can sometimes take several minutes, and a couple refreshes to actually see the newest content using content. Locally it happens in a matter of seconds, but in production (SSR full static) - it takes too long...

My current implementation:

    async fetch() {
      this.latestArticles = await this.$content('articles').sortBy('published', 'desc').limit(5).fetch()
      this.latestAnalyses = await this.$content('analysis').sortBy('published', 'desc').limit(5).fetch()
      this.latestBlog = await this.$content('blog').sortBy('published', 'desc').limit(10).fetch()
}

and then in a computed property, I mash them together like so:

      latestContent() {
        return this.latestArticles.concat(this.latestAnalyses,this.latestBlog).sort(function(a, b) {
          var keyA = new Date(a.published), keyB = new Date(b.published);
          // Compare the 2 dates
          if (keyA < keyB) return 1;
          if (keyA > keyB) return -1;
          return 0;
        })
      }

I'm pretty sure it's not the latestContent, because my $fetchState.pending block runs throughout that entire time.

Again, this is for target: static, so I would assume even during the build process there should be something there at first, yes?

azrikahar commented 3 years ago

Hi @jhull, if I understand correctly, is your aim just to get the latest articles, analyses & blog posts like in a home page of some sort?

Another question is may I know what does the $fetchState.pending block shows? If this is intended as a full static site, the usage seems unusual here. Though it can definitely be a valid use case depending on what you want to achieve or show to the users, hence I would like to understand clearer.

EDIT: Just a quick follow up question, is there a reason for not using asyncData() method here?

jhull commented 3 years ago

I could be using it wrong...didn't really consider the thinking behind it. And yes, it's a "home page" of sorts in much the same way you describe. I figured this was a typical use case...

fetchState is a simple spinner that lets users know its loading the latest content.

Right now, I create a new piece of content and rebuild the whole site. But I can see a future where I just upload that one file change and not have to rebuild the entire site.

And I've never really understood difference between asyncData and fetch. They seem the same, with the latter being the more modern up-to-date approach.

azrikahar commented 3 years ago

No worries. There's nothing wrong with using fetch() to get the data on client side when they visit the page, and this is definitely a valid use case depending on situations. However, I do believe you will benefit more with using asyncData() without having to wait for the fetch() in this particular scenario :)

And I've never really understood difference between asyncData and fetch. They seem the same, with the latter being the more modern up-to-date approach.

Rest assured I was not sure of their difference in the beginning as well haha. I might be oversimplifying here, but I think we can think asyncData() as being done before the page is served to the user. Which means what ever is done in there, won't be done on the client side at all. Though this is true for SSR, it behave slightly different when you are using full static. When you run nuxt generate command, Nuxt have a crawler that will go through each pages/links and run the asyncData() once and generate the final HTML files.

As for fetch(), the default behaviour is running it once on the server unless you disable it using fetchOnServer: false (reference) in SSR. However in your case, you are using full static, so it means it will only ever run on client side. This means whenever someone visits the homepage, they will fetch from the built API that is served by your generated files, do 3 await calls then run sort over each item in the array which can be pretty taxing in terms of processing.

Do note that I mentioned *built API files, which means they are static and are baked in to your dist folder, and the fetch() are technically fetching from the built static files, not directly from your GitHub repo. So when you mentioned you want to upload one file change and not have to rebuild the entire site, it will not be possible with this approach. With that said, many static site hosting services like Netlify does provide github repo integration, so you don't have to worry about manually rebuilding the site. You just need to push a commit and the rebuild is done automatically for you.

Sorry if the explanation is too much. Just try out the proposed solution below and see how it is for you.


Proposed solution

I am going to translate your existing code to asyncData() here, so I'll try to mimic your code since I can't truly see the whole source code. Hope it's roughly accurate here.

Your current code

<template>
  <template v-if="$fetchState.pending">
    <!-- Show the spinner here -->
  </template>
  <template v-else>
    <!-- Loop through **latestContent** computed array here to show the articles -->
  </template>
</template>

<script>
export default {
  data() {
    return {
      latestArticles: [],
      latestAnalyses: [],
      latestBlog: [],
    }
  },
  async fetch() {
    // the 3 await $content calls here
  },
  computed: {
    latestContent() {
      // your sorting code here
    }
  }
}

Using asyncData() for full static without any fetching needed on client side

<template>
  <div>
    <!-- Loop through **latestPosts** data array here to show the articles -->
  </div>
</template>

<script>
export default {
  async asyncData({ $content }) {
    const results = await Promise.all([
                                     $content('articles').sortBy('published', 'desc').limit(5).fetch(),
                                     $content('analysis').sortBy('published', 'desc').limit(5).fetch(),
                                     $content('blog').sortBy('published', 'desc').limit(10).fetch()
                             ])
    const unsortedPosts = [].concat(...results)
    const sortedPosts = unsortedPosts.sort((a, b) => {
          const keyA = new Date(a.published), keyB = new Date(b.published);
          // Compare the 2 dates
          if (keyA < keyB) return 1;
          if (keyA > keyB) return -1;
          return 0;
        })
    return { latestPosts: sortedPosts }
  },
}

The latestPosts returned in asyncData will be added to the data() option, that's why we don't even need to specify data() like in fetch example anymore. And since you are doing the sorting during build process, your client side never needs to run any fetching or sorting and should be able to instantly see the posts in your homepage.

Further explanation:

Would you mind giving this solution a try? Feel free to change the variable names I used to what you see fit.

jhull commented 3 years ago

I really really appreciate the in-depth description!

I followed it exactly and it worked great locally, but in production (Netlify SSR) it's empty...

azrikahar commented 3 years ago

Glad it's helpful. Btw I think we shouldn't categorize Netlify as doing SSR since it's not a compute like Heroku running Nodejs for server side rendering. It can only serve/host full static sites or a SPA.

Since it worked locally, we'll need more info to work with here.

These are just the debugging steps I can think of. Since it worked locally but not in Netlify, we should try to narrow down is it the /dist folder (or generated static files) being incorrect, or you're passing something else to Netlify here. Another possibility is I suspect you are doing target: server so it will work locally (since your machine is running nodejs for it to do SSR) and you passed the files to Netlify that can't do SSR. Another possibility is you are running build instead of generate. Do kindly provide more info so it's easier for us to work out the issue :)

jhull commented 3 years ago

Thank you so much for the detailed response. So very much appreciated. 😃

I'm using target: 'static', and the Netlify config is as suggested with the exception of using yarn generate instead of npm run generate.

When it runs locally, yes that's when I use yarn dev. When I do yarn generate and then yarn start, that "home page" is indeed blank. Looking at the generated file, it's a ton of minified CSS with some script at the end - no content. So there is something weird with the build process (though it is building it).

The only thing "odd" I can think of is that I'm using a handful of Promise-based functions - these work as expected...

Here is the pertinent info from nuxt.config.js:

export default {
  mode: 'universal',
  target: 'static',
  generate: {
    routes: buildRoutes,
    fallback: true,
    exclude: [
      /^\/settings/,
    ]
  },
}

If that "home page" has a middleware: auth on it, could that be causing it to not render?

azrikahar commented 3 years ago

If that "home page" has a middleware: auth on it, could that be causing it to not render?

Hmm I'm not particular sure about this, but just to be clear, does the auth means no public access at all? And when you tested locally, are you authenticated? If yes, does it work if you're not authenticated?

This might be difficult if you have to redact a lot of sensitive data, but would you be able to provide a codesandbox reproduction environment?

Tahul commented 2 years ago

Hello 🙂

Has your issue been fixed?

I think this is still relevant for v2, so I'm keeping this open, would love to see performances benchmarks between v1 and v2!