Contentful in Vue component (nuxt.js)

Hello,
I was following the guide how to set up Contentful with Vue+Nuxt. Everything works perfectly as long as content is loaded directly in a page.
As soon as I try to create a component with content from contentful, I always get the error Can not read property 'fields' of undefined.
Do I have to pass person and posts as props in some way?

index.vue

...

<content></content>

...

content.vue

...

<h1>{{ person.fields.name }}</h1>

...

<script>
  import {createClient} from '~/plugins/contentful.js'

  const client = createClient()

  export default {
    // `env` is available in the context object
    asyncData ({env}) {
      return Promise.all([
        // fetch the owner of the blog
        client.getEntries({
          'sys.id': env.CTF_PERSON_ID
        }),
        // fetch all blog posts sorted by creation date
        client.getEntries({
          'content_type': env.CTF_BLOG_POST_TYPE_ID,
          order: '-sys.createdAt'
        })
      ]).then(([entries, posts]) => {
        // return data that should be available
        // in the template
        return {
          person: entries.items[0],
          posts: posts.items
        }
      }).catch(console.error)
    }
  }
</script>

Hello Simon. :wave:t2:

Hmm… Are the request made properly and return the data?
I see that the code you pasted is more or less the code from the tutorial.

env is available in the context object
asyncData ({env}) {

Without having the code in front of me I’d say that env and asyncData are nuxt specific things and are not available in a vue.js component. The approach there has to be sightly different.

Vue component docs https://vuejs.org/v2/api/#Vue-component.

Quick google search for fetching data in Vue components returns something like this. :point_right:t2: https://www.sitepoint.com/fetching-data-third-party-api-vue-axios/

This means that if you want to fetch data in a single component not being connected to nuxt you have to either pass down the config to this component or make env available globally.

Hope this helps.

1 Like

Hi Stefan, thanks for the quick answer!
Everything worked perfectly before extracting the contentful-related lines into a component.

Not sure if I get what you mean by “component not being connected to nuxt”.
I want to fetch the content on generation of the static page with nuxt, not on pageload, so it should be connected to nuxt.

env not being available might be a point :thinking:

I got this now:

[Vue warn]: Property or method is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.

I see this is probably more a question for the Nuxt/Vue community than a Contentful pro.
:+1:

Hey Simon!

Nuxt does some magic for the page components. asyncData is not a vue thing and is only available in nuxt page components. When you extract it to a view component this doesn’t work anymore.
That’s why I would assume that env is also not available then anymore. :slight_smile:

That said if you want to do the calls inside of “independent vue components” inside of “nuxt page components” you have to restructure a few things.

It’s hard for me to give advice on that though as I don’t have the code in front of me right now. :frowning:

I see this is probably more a question for the Nuxt/Vue community than a Contentful pro.

:+1:t2:

Hopes that makes the problem a bit clearer. :slight_smile:

1 Like

Okay, thanks!
I just started learning Vue+Nuxt, sorry for being so noobish :blush:
But this should be solvable once I find out how to do the restructuring you suggested.

(If you really want, feel free to have a look at https://github.com/simonhermann/mx-nuxt :angel: )

No problem Simon, I’m always happy to help. :slight_smile:

I’m Berlin based so it’s a bit late here, but I’ll see if I can have a quick look tomorrow.

Good luck!

Same for me, actually :first_quarter_moon_with_face:
(Ich arbeite etwa 1 km nördlich eures Hauptquartiers; )

1 Like

Got it working by moving the contentful script to index.vue and passing through the props.
But I’m pretty sure this is not the ‘right’ solution, since data from contentful is now only available in index and no other pages. It defies the whole purpose of working with components…:thinking:

index.vue

...
<content v-bind:person="person" v-bind:posts="posts"></content>
...
<script>
import Content from './Content'
import {createClient} from '~/plugins/contentful.js'

const client = createClient()

export default {
  components: {
    'content': Content
  },

  // `env` is available in the context object
  asyncData ({env}) {
    return Promise.all([
      // fetch the owner of the blog
      client.getEntries({
        'sys.id': env.CTF_PERSON_ID
      }),
      // fetch all blog posts sorted by creation date
      client.getEntries({
        'content_type': env.CTF_BLOG_POST_TYPE_ID,
        order: '-sys.createdAt'
      })
    ]).then(([entries, posts]) => {
      // return data that should be available
      // in the template
      return {
        person: entries.items[0],
        posts: posts.items
      }
    }).catch(console.error)
  }
}
</script>

Content.vue

...
<h1>{{ person.fields.name }}</h1>
...
<script>
export default {
  props: [
    'person',
    'posts'
  ]
}
</script>
...
1 Like

Moin. :wave:t2:

Ja I see, that works but is not the best approach. :wink:

These calls should go into the component itself.

client.getEntries({
  'sys.id': env.CTF_PERSON_ID
})

This needs some restructuring of the overall Contentful integration, but you’re right, I should add this to the tutorial. :slight_smile:

By the way, I’m dreaming of a vue contentful component that abstracts the sdk calls away.

<contentful c-query="{ 'sys.id': ... } as items">
  <h1>{{ items[0].fields.title }}</h1>
</contentful>

But unfortunately haven’t had the time to kick it off. Maybe wanna collab on sth like that?

(no pressure though)

1 Like

Thank you for this solution :+1:

In the meantime I also found the time to describe a way to go with scoped slots in vue.

Maybe that’s helpful, too. :slight_smile: