Image for post
Image for post

Build a website using Nuxt & Contentful — A step by step guide

Nicky Christensen
May 31, 2019 · 11 min read

What is Nuxt

What is Contentful:



Nuxt Setup

npm install -g vue-cli
vue init nuxt/starter nuxt-contentful-demo
Image for post
Image for post
npm install
npm run dev

Contentful Setup

Image for post
Image for post
Image for post
Image for post

Integrate Contentful into the Nuxt

npm install --save contentful
Image for post
Image for post
const contentful = require('contentful');

// use default environment config for convenience
// these will be set via `env` property in nuxt.config.js

const
config = {
space: process.env.CTF_SPACE_ID,
accessToken: process.env.CTF_CDA_ACCESS_TOKEN
};


// export `createClient` to use it in page components
module.exports = {
createClient () {
return contentful.createClient(config)
}
}
const config = {
space: process.env.CTF_SPACE_ID,
accessToken: process.env.CTF_CDA_ACCESS_TOKEN
};
{
"CTF_SPACE_ID": "YOURSPACEID",
"CTF_CDA_ACCESS_TOKEN": "YOURACCESSTOKEN",
"CTF_ENVIRONMENT": "master"
}
// ./nuxt.config.js
const config = require('./.contentful.json')

module.exports = {
// ...
env: {
CTF_SPACE_ID: config.CTF_SPACE_ID,
CTF_CDA_ACCESS_TOKEN: config.CTF_CDA_ACCESS_TOKEN,
CTF_ENVIRONMENT: config.CTF_ENVIRONMENT
}
// ...
}

Build content in Contentful

Image for post
Image for post
Image for post
Image for post
Image for post
Image for post

Creating the navigation

// Navigation.vue<template>
<div class="navigation">
<nav>
<ul role="menu">
<li v-for="(navItem, index) in pages" :key="index">
<nuxt-link :to="'/' + navItem.fields.slug.trim()" role="menuitem">{{navItem.fields.navTitle}}</nuxt-link>
</li>
</ul>
</nav>
</div>
</template>

<script>
export default {
name: 'Navigation',
props: {
pages: {
type: Array, // We expect an array of pages that we need for our navigation
required: true
}
}
}
</script>
//pages/index.vue<template>
<section class="container">
<Navigation :navItems="pages" />
</section>
</template>

<script>
import Navigation from '../components/Navigation';
export default {
components: {
Navigation
}
}
</script>

Fetch data from Contentful

Image for post
Image for post
import { createClient } from '../plugins/contentful';
const contentfulClient = createClient();
//pages/index.vueasyncData ({env}) {
return Promise.all([
// fetch all blog posts sorted by creation date
contentfulClient.getEntries({
'content_type': 'page',
order: '-sys.createdAt'
})
]).then(([pages]) => {
// return data that should be available
// in the template
return {
navItems: pages.items
}
}).catch(console.error)
}
<template>
<section class="container">
<Navigation />
<div class="container__content">
<h1>Please select a page you wish to view</h1>
<p>This is a website for demo purposes of using Nuxt & Contentful together</p>
</div>
</section>
</template>
<script>
import Navigation from '../components/Navigation';
import {createClient} from '../plugins/contentful';
const contentfulClient = createClient();
export default {
components: {
Navigation
},
asyncData ({env}) {
return Promise.all([
// fetch all blog posts sorted by creation date
contentfulClient.getEntries({
'content_type': 'page',
order: '-sys.createdAt'
})
]).then(([pages]) => {
// return data that should be available
// in the template
return {
pages: pages.items
}
}).catch(console.error)
}
}
</script>
Image for post
Image for post
Image for post
Image for post
Image for post
Image for post
//pages/_id/index.vue<template>
<div class="page-component">
<p>This is the page component</p>
</div>
</template>

<script>
export default {
name: 'index'
}
</script>
Image for post
Image for post
import {createClient} from '../../plugins/contentful';
const contentfulClient = createClient();
asyncData ({ env, params }) {
return contentfulClient.getEntries({
'content_type': page,
'fields.slug': params.id // the magic happens here
}).then(page => {
return {
page: page.items[0]
}
}).catch(console.error)
}
// pages/_id/index.vue<template>
<div class="page-component">
<a @click="$router.go(-1)">Go back to overview</a>
<hr />
<h1>{{page.fields.heading}}</h1>
<img :src="page.fields.image.fields.file.url" :alt="page.fields.heading" v-if="page.fields.image" />
<p>
{{page.fields.content}}
</p>
</div>
</template>

<script>
import {createClient} from '../../plugins/contentful';
const contentfulClient = createClient();

export default {
name: 'index',
asyncData ({ env, params }) {
return contentfulClient.getEntries({
'content_type': 'page',
'fields.slug': params.id
}).then(page => {
return {
page: page.items[0]
}
}).catch(console.error)
}
}
</script>
Image for post
Image for post

Almost there…

//layout/default.vue<template>
<div>
<Navigation></Navigation>
<nuxt/>
</div>
</template>
<script>
import Navigation from '../components/Navigation';

export default {
components: {
Navigation
}
}
</script>

Go Vuex

//store/index.jsimport Vuex from 'vuex';
import navigation from './modules/navigation';

const createStore = () => {
return new Vuex.Store({
modules: {
navigation: { ...navigation, namespaced: true }
},
strict: false
})
};

export default createStore
//store/modules/navigation.jsimport {createClient} from '../../plugins/contentful';
const contentfulClient = createClient();
export const state = {
navItems: null
};

export const mutations = {
setMenuItems(state, data) {
state.navItems = data;
}
};

export const actions = {
getPageItems({commit}) {
contentfulClient.getEntries({
'content_type': page,
order: '-sys.createdAt'
}).then((page) => {
if(page) {
const navItems = page.items;
commit('setMenuItems', navItems);
}
}).catch((err) => {
console.log("error", err);
});
}
};

export default {
state,
mutations,
actions,
};
//components/Navigation.vue<template>
<div class="navigation">
<nav>
<ul role="menu">
<li v-for="(navItem, index) in navItems" :key="index">
<nuxt-link :to="'/' + navItem.fields.slug.trim()" role="menuitem">{{navItem.fields.navTitle}}</nuxt-link>
</li>
</ul>
</nav>
</div>
</template>

<script>
import {mapState} from 'vuex';
export default {
name: 'Navigation',
computed: {
...mapState('navigation', [
'navItems'
])
},
mounted() {
this.$store.dispatch("navigation/getPageItems");
}
}
</script>
Image for post
Image for post
Image for post
Image for post

You’re at the finish line

Next step:


Vue.js Developers

Helping web professionals up their skill and knowledge of…

Nicky Christensen

Written by

Frontend Tech Lead @ Enversion— VueJS Fanboy — In love with most frontend related stuf — nickychristensen.dk / https://twitter.com/nickycdk

Vue.js Developers

Helping web professionals up their skill and knowledge of Vue.js

Nicky Christensen

Written by

Frontend Tech Lead @ Enversion— VueJS Fanboy — In love with most frontend related stuf — nickychristensen.dk / https://twitter.com/nickycdk

Vue.js Developers

Helping web professionals up their skill and knowledge of Vue.js

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store