Tanker og erfaringer
fra en frontend-udvikler

Byg et website med Nuxt og Contentful — en trin-for-trin guide

Skrevet af
Nicky Christensen Nicky Christensen
Udgivet
Læsetid
14 min
nuxtvuewebudvikling

Bemærk: Denne artikel dækker Nuxt 2 og Contentful. Hvis du er interesseret i Nuxt 3, kan du læse mine andre artikler om det nyeste fra Nuxt.

Som frontend-udvikler er det en fornøjelse at arbejde med static-site-generators og serverless-arkitektur, og med det kan vi skabe meget kraftfulde og fantastiske applikationer, som vi også kan server-side rendere.

Denne artikel har til formål at give dig en trin-for-trin guide til at bygge et meget basalt website med NuxtJS + Contentful — inklusiv et simpelt Vuex-eksempel.

Find det fulde GIT-repo her:https://github.com/nickycdk/nuxt-contentful-example

Personligt kører mit eget website på et Nuxt/Contentful-setup med Continuous deployment ved at forbinde mit GIT-repo med Netlify, dette kombineret med Contentfuls webhooks til automatisk at genbygge dit site, når der publiceres nyt indhold, er bare... Fantastisk

Hvad er Nuxt

Hvis du har bygget Vue-applikationer før, har du sandsynligvis hørt om NuxtJS, som er det tilsvarende af, hvad NextJS er for React.

Nuxt er et framework, der bygger oven på Vue og forenkler udviklingen af universelle eller single page Vue-apps, hvilket er fantastisk, hvis du bygger et website og vil sikre, at det kan blive indekseret af Google.

Derick Sozo har skrevet et godt indlæg om, hvorfor du bør vælge Nuxt til din næste webapplikation, som kan findes her: https://medium.com/vue-mastery/10-reasons-to-use-nuxt-js-for-your-next-web-application-522397c9366b

Hvad er Contentful:

Contentful er kendt som et headless CMS-system, hvilket betyder, at det er et API-first content-management-system, hvorfra du kan oprette, administrere og distribuere indhold til enhver platform eller enhed.

Lær meget mere om Contentful på deres egen hjemmeside lige her: https://www.contentful.com/

I denne artikel lærer du, hvordan du bygger et meget simpelt Nuxt-website, der henter data fra Contentful. Når du har fået styr på begge dele, og hvordan du kan bruge disse to sammen, kan du virkelig begynde at bygge kraftfulde og fantastiske applikationer.

Nuxt-opsætning

Før vi kan begynde at bygge, skal vi installere Nuxt. Det gør vi ved at bruge VueCLI. Hvis du ikke har installeret dette på dit system før, skal du installere det via terminalen:

npm install -g vue-cli

Nu kan du bruge VueCLI til at opsætte et Nuxt-projekt.

vue init nuxt/starter nuxt-contentful-demo

Følg instruktionerne og giv projektet et navn, en beskrivelse og en forfatter.

Når du er færdig, cd ind i mappen til dit projekt og kør

npm install
npm run dev

Smukt! Vi er nu et skridt tættere på, og vi har et fundament at bygge videre på.

Contentful-opsætning

Gå til Contentful og log ind med dit brugernavn og din adgangskode. Hvis du ikke allerede har en bruger, skal du oprette en for at kunne bruge Contentful. De har en gratis plan, som du kan bruge.

Når du er logget ind, er det første, vi skal gøre, at opsætte et nyt space til vores website.

Når du er i Contentful, klik på "Create space"

Når du opretter et nyt space, skal vi udfylde et par detaljer. Vælg den gratis plan, giv dit space et navn og bekræft oprettelsen.

Integrer Contentful i Nuxt

Når vi vil bruge Contentful i Nuxt-projekter, skal vi installere JavaScript SDK'et. Det kan vi gøre ved at køre følgende kommando:

npm install --save contentful

Når installationen er færdig, kan vi gå til vores projekt i vores IDE og oprette en ny fil under "plugins". Denne fil fortæller grundlæggende Nuxt, at den skal bruge Contentful som et plugin, hvilket gør det muligt for os nemt at hente vores data fra Contentful.

Gå videre og opret en ny fil:

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)
  }
}

Som du måske har bemærket, refererer vi til nogle environment-variabler, som vi ikke har oprettet endnu.

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

For at dette virker, skal vi oprette en ny fil "contentful.json", som vi placerer i vores rodmappe. Denne fil skal indeholde noget konfiguration.

{
  "CTF_SPACE_ID": "YOURSPACEID",
  "CTF_CDA_ACCESS_TOKEN": "YOURACCESSTOKEN",
  "CTF_ENVIRONMENT": "master"
}

Du kan finde disse indstillinger ved at navigere til > Settings > Api Keys i Contentful-dashboardet.

Når du er færdig, gem filen og gå til nuxt.config.js

Vi skal require den nyoprettede konfigurationsfil og tilføje lidt kode til vores nuxt.config.js-fil

// ./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
  }
  // ...
}

env-egenskaben er en måde at definere værdier på, der vil være tilgængelige, når du bruger process.env, når sitet køres i en Node.js-kontekst.*

Nu hvor vi har fået alle de grundlæggende ting sat op til at bruge Contentful, er næste skridt at oprette noget indhold, vi kan trække ind i vores Nuxt-applikation.

Byg indhold i Contentful

Før vi kan hente indhold ind i applikationen, skal vi oprette en content-type og noget indhold. Start med at navigere til fanen: Content Model og opsæt en ny content-type

Når content-typen er oprettet, skal vi tilføje nogle felter til den. I dette eksempel opsætter vi en meget basal model med følgende felter:

Dernæst skal vi bruge et par sider. Gå videre og opret nogle sider baseret på den oprettede content-type. Du kan gøre dette under fanen "Content".

Fantastisk — Nu har vi noget indhold oprettet, og vi er klar til at lave noget mere arbejde i vores kode.

Opret navigationen

Vores website har brug for en navigation, så vores brugere kan navigere mellem sider. I "Components"-mappen opretter du en ny komponent kaldet "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>

Gå dernæst til mappen "pages" og åbn filen index.vue. I denne fil skal vi inkludere vores navigationskomponent, så vi kan tilføje den til filen.

//pages/index.vue
<template>
  <section class="container">
    <Navigation :navItems="pages" />
  </section>
</template>

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

Som du måske bemærker, sender vi i Navigation.vue en prop kaldet navItems med data, der ikke eksisterer endnu. For at sende pages ned til navigationskomponenten skal vi først hente dataen fra Contentful.

Hent data fra Contentful

Det første, vi skal gøre, er at importere klienten fra det Contentful-plugin, vi oprettede tidligere i plugins-mappen:

Tilføj følgende til koden:

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

Dernæst skal vi bruge asyncData-metoden. Denne giver os mulighed for at hente og rendere dataen server-side. I denne henter vi alle sider oprettet i Contentful.

*Interesseret i at lære mere om asyncData: *https://nuxtjs.org/api/

//pages/index.vue
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 {
          navItems: pages.items
        }
      }).catch(console.error)
}

Det der sker er, at vi starter med at hente alt indhold oprettet med content-typen page sorteret efter oprettelsesdato.

Når vi har dataen, tildeler vi den til egenskaben pages, som også er prop-dataen, der sendes til vores navigationskomponent:

Din index.vue-fil bør nu ligne noget i denne retning:

<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>

Og din side ser nogenlunde sådan ud:

Wauw

Var nødt til at inkludere et meme til dette! Ok, måske ikke det pæneste, men igen, style og design som du synes :)

Anyway...... så langt, så godt... Hvad sker der nu, hvis du prøver at klikke på et af navigationspunkterne? Argh, du får en fejlside

Lad os fixe dette. Problemet er, at Nuxt automatisk leder efter en komponent med samme navn som child-routen, hvilket betyder, at hvis du har en URL som: /about — vil Nuxt lede efter en about.vue-komponent eller en mappestruktur som /about/index.vue

For at fixe dette skal vi oprette en ny komponent i "pages/_id" — kald den index.vue Det fortæller Nuxt at bruge denne komponent til alle child-routes, f.eks.: /about

Vi kan nemt teste det og se, om det virker ved at tilføje noget hardkodet HTML:

//pages/_id/index.vue
<template>
  <div class="page-component">
    <p>This is the page component</p>
  </div>
</template>

<script>
    export default {
        name: 'index'
    }
</script>

Når du nu klikker på et af linkene i vores navigation, bør du se noget som dette:

Næste skridt er at hente indholdet af den aktuelle side. Igen importerer vi fra vores Contentful-plugin og tildeler det til en variabel

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

Og igen skal vi bruge asyncData-metoden, men denne gang henter vi dataen ved at matche sluggen og parametrene fra URL'en:

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)
}

Nu har vi adgang til dataen i egenskaben: page. Du kan nu oprette din template HTML og style den, som du vil — den fulde komponent bør nu se nogenlunde sådan ud:

// 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>

Og din side ser nogenlunde sådan ud:

Næsten i mål...

Vi har nu et fungerende website, men det mangler stadig et par ting. Navigationen mangler, når vi kigger på en side, og når vi er på forsiden, har vi ingen kontrol over rækkefølgen af vores navigationspunkter. Ikke ligefrem det bedste UX-mønster. Lad os også fixe dette.

Flyt navigationskomponenten ud af vores "pages/index", og placer den i "layouts/default", og fjern de props, der sendes til vores navigationskomponent (husk også at fjerne dem inde i selve komponenten)

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

Desværre tillader Nuxt os ikke at bruge asyncData-metoden i layoutet. Hvis vi prøver, får vi en fejl, når vi henter indhold fra Contentful. Mere om det her: https://github.com/nuxt/nuxt.js/issues/1740

Brug Vuex

I dette tilfælde bruger vi Vuex til at løse vores problem. Gå til store-mappen og opret en index.js-fil.

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

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

export default createStore

Som du kan se, importerer vi en fil kaldet navigation, der ikke eksisterer endnu, så vi skal oprette den og lave lidt arbejde:

//store/modules/navigation.js
import {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,
};

Bemærk: Husk at fjerne al asyncData fra pages/index.vue, da vi ikke længere har brug for det

Vi opretter en action, der committer vores navigationspunkter. Når vi committer, muterer vi staten og gemmer grundlæggende navigationspunkterne i staten.

Dernæst skal vi refaktorere vores navigationskomponent til at hente data fra storen.

//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>

Når du nu tjekker, bør vores navigation være fuldt funktionel igen, og når du går til en side, vil vores navigation være synlig.

For at kontrollere rækkefølgen af navigationspunkterne kan du tilføje et order-felt i din content-type. Det er også muligt at oprette en separat content-type til navigationspunkter, oprette en global "container" og bruge reference-feltet i Contentful.

Der er masser af forskellige måder, du kan gøre dette på, og det afhænger alt sammen af, hvordan du vælger at strukturere og administrere dine data i Contentful, da der ikke er nogen specifikke retningslinjer for, hvordan man strukturerer indhold.

Du er i mål

Du har nu bygget dit første meget basale website med Nuxt og Contentful, og selvom dette er et meget basalt eksempel, håber jeg, du får idéen om, hvordan du kan bruge disse to platforme til at skabe meget kraftfulde og fantastiske applikationer.

Find det fulde repo her: https://github.com/nickycdk/nuxt-contentful-example

Næste skridt:

Nu hvor du har fået styr på det grundlæggende, opfordrer jeg dig til at prøve at bygge et rigtigt website med det, du har lært.

I fremtiden vil jeg prøve at dække emner som

  • Hvordan du forbinder og deployer dit statiske site til Netlify
  • Hvordan du genererer et sitemap med Nuxt
  • Generering af filer fra dynamiske routes
  • Rendering af markdown fra dine indholdsfelter i Contentful
  • Rendering af indhold fra et WYSIWYG-felt i Contentful
  • Og meget mere...

Har du brug for en erfaren udvikler?

Lad os tage en snak om hvordan jeg kan hjælpe med teknisk strategi, arkitektur og frontend-udvikling.

Kontakt mig