Bruk Wordpress som headless CMS, med Kurts Gatsby-løsning

- Noe må man finne på når sommeren regner bort, forteller Kurt Lekanger.

Kurt Lekanger var inntil nylig redaktør i Digi.no, og er en ivrig hobbykoder, med ny jobb i Iteo. 📸: Iteo.no
Kurt Lekanger var inntil nylig redaktør i Digi.no, og er en ivrig hobbykoder, med ny jobb i Iteo. 📸: Iteo.no Vis mer

Noe må man finne på når sommeren regner bort. Ett av mine småprosjekter denne sommeren har vært å lage en mal/«starter» som gjør det noenlunde enkelt og kjapt å komme igang med å lage websider basert på det hotteste innenfor moderne web-utvikling – kombinert med gode, gamle Wordpress.

Ut med PHP – inn med React!

Rundt én tredjedel av verdens nettsteder kjører på Wordpress, noe som gjør det til verdens desidert mest populære publiseringsplattform (CMS). Men selv om Wordpress er en svært moden, robust og brukervennlig plattform, er det likevel mange som ønsker å kunne ta i bruk moderne web-teknologier med Javascript-biblioteker som for eksempel React.js – som raskt har blitt den mest populære måten å lage web-applikasjoner på.

Det er fullt mulig å få det beste fra begge verdener ved å benytte Wordpress som et såkalt «headless CMS». Da blir Wordpress backendløsningen som håndterer alt innholdet, og du står fritt til å lage frontenden i for eksempel React – eventuelt Vue eller noe annet.

«Det er fullt mulig å få det beste fra begge verdener ved å benytte Wordpress som et såkalt «headless CMS».»

De som produserer innholdet til nettsiden kan fortsette å bruke de kjente og kjære redigeringsverktøyene i Wordpress slik de alltid har gjort, mens du som frontendutvikler kan bruke de verktøyene som passer best for å lage nettsiden eller appen.

Ved å skille backend og frontend på denne måten, altså koble CMS-et fra nettsiden, får du en mye mer fleksibel løsning der data fra den samme Wordpress-installasjonen kan benyttes på flere ulike nettsteder, og på ulike enheter – for eksempel i en mobilapp.

Wordpress har lenge hatt et REST-API som gjør det mulig å hente ut data fra publiseringsløsningen, men de siste månedene har det skjedd mye som gjør det enda smidigere å integrere egne løsninger mot Wordpress ved hjelp av spørrespråket GraphQL.

Løsningen jeg har laget baserer seg på Javascript/React-rammeverket Gatsby, en plugin for Gatsby og et par andre plugins for Wordpress. Målet var å lage en «starter» med et minimum av «boilerplate»-kode man trenger for å hente ut data fra Wordpress – i hovedsak innlegg, sider og sideinformasjon (som tittel og beskrivelse).

Selv om det er mange måter å gjøre dette på, er metoden jeg har brukt – og beskriver her – den som anbefales offisielt av Gatsby-teamet for bruk av Wordpress med Gatsby.

For de som ikke gidder å lese mer – her er starteren på Github: github.com/klekanger/wordpress-gatsby-starter

Hvorfor Gatsby?

React er som sagt den mest populære måten å bygge web-applikasjoner på, og er utviklet som et åpen kildekodeprosjekt av Facebook. Også GraphQL er det Facebook som står bak. En variant av React kalt React Native kan brukes til å lage «native» apper til Android og iOS, og er enkelt å komme igang med hvis du behersker React.

I mitt lille prosjekt – og også for min personlige nettside lekanger.no har jeg som nevnt brukt et rammeverk kalt Gatsby – som baserer seg på React. Gatsby er en såkalt «statisk sidegenerator» (static site generator) som er svært godt egnet til å lage for eksempel firmanettsider, informasjonsnettsider, blogger osv.

Det er flere fordeler med å bruke Gatsby-rammeverket fremfor å bygge opp nettstedet fra bunnen av med React.js:

  • Gatsby genererer ekstremt kjappe nettsider, og rammeverket gjør også utviklingsjobben mye enklere.
  • SEO – søkemotoroptimalisering. Gatsby-nettsider rendres på serveren, slik at Google og andre søkemotorer ser fiks ferdige nettsider og kan indeksere alt innholdet. At Gatsby-nettsider er optimalisert med tanke på hastighet, blant annet med automatisk optimalisering av bilder, bidrar også til å sende Gatsby-nettstedet ditt oppover på søkemotor-rankingen.
  • Sikkerhet er et annet argument for å bruke Gatsby mot Wordpress sine API-er. Gatsby-nettsidene leveres som statiske HTML-filer fra serveren til nettleseren, så det er faktisk ikke mulig for noen å se at det er Wordpress som leverer innholdet til nettsidene. Ergo er det heller ikke mulig for hackere å utnytte sårbarheter i for eksempel Wordpress-plugins. Nettsiden din forholder seg til Wordpress kun via API-er i det nettsiden bygges.
  • I Gatsby kan du hente data fra et hav av ulike kilder ved å installere ulike plugins, for eksempel en plugin som henter data fra Wordpress – som er det jeg tar for meg her. Dataene henter du ved hjelp av GraphQL-spørringer – uansett om dataene kommer fra filsystemet, fra en RSS-feed eller fra et headless CMS som for eksempel Sanity, Contentful eller fra Wordpress.

Det siste punktet er viktig. Selv om jeg bruker Wordpress i min løsning, er det bare å installere en annen plugin og lage nye GraphQL-spørringer for å hente inn data fra andre kilder. Det aller meste av Javascript/React-koden du har laget ved hjelp av Gatsby kan du gjenbruke.

En ulempe med Gatsby tidligere var at du måtte kjøre en ny byggeprosess for å generere alle de statiske HTML-filene nettsiden består av, hver gang innhold skulle endres. Gatsby fikk imidlertid nylig støtte for incremental builds, som støttes av den løsningen jeg har laget. Det betyr at endringer du gjør i Wordpress (skrive eller redigere innlegg og sider) nesten umiddelbart blir synlige på Gatsby-nettstedet. Gatsby «lytter» etter endringer, og oppdaterer ved behov kun de delene av nettsiden som er endret.

Det er på tide å bli litt mer teknisk, og gå litt nærmere inn på hvordan du lager en nettside i Gatsby som henter data fra Wordpress via GraphQL. Du bør ha litt erfaring med React, og hvis du ikke har vært borti Gatsby før anbefales den offisielle Gatsby-dokumentasjonen. Den er rett og slett fantastisk bra, og har utmerkede trinn-for-trinn-instruksjoner og «oppskrifter» for det meste du måtte lure på.

Sette opp en lokal Wordpress-installasjon for testing

For å ha noe å utvikle på, satte jeg opp en testinstallasjon av Wordpress lokalt på min Windows-PC med WSL 2 og Ubuntu Linux. Jeg valgte å sette opp Wordpress i en Docker-container, men du kan også bruke Local by Flywheel (localwp.com/ ) for å få en lokal Wordpress-server opp å stå på maskinen din. Eventuelt sette opp et Wordpress-testnettsted på wordpress.com.

Microsoft har informasjon om hvordan du legger inn Ubuntu for Windows og oppdaterer Ubuntu til å bruke WSL 2 i stedet for WSL 1 – noe som er nødvendig for integrasjon mot Docker: docs.microsoft.com/en-us/windows/wsl/install-win10

Docker Desktop laster du ned herfra: docker.com/products/docker-desktop

I tillegg må du sette opp Node.js til å fungere i WSL 2 – det finner du informasjon om her: docs.microsoft.com/en-us/windows/nodejs/setup-on-wsl2

For å bruke mitt prosjekt (som ligger på Github) som utgangspunkt, kan du klone (eventuelt laste ned i .zip-format) wordpress-gatsby-starter fra min Github-side. Åpne opp en WSL-terminal, evt. terminalvindu i Mac OS, og kjør git clone på denne måten:

git clone https://github.com/klekanger/wordpress-gatsby-starter.git

Gå deretter til /wordpress-gatsby-starter/wp-docker, der jeg har inkludert en .yml-fil som kan brukes av Docker Compose for å sette opp Docker-containere med Wordpress og MySQL. Kjør følgende kommando:

docker-compose up -d

Åpne Docker Dashboard for å sjekke at containerne kjører som de skal.

image: Bruk Wordpress som headless CMS, med Kurts Gatsby-løsning

Gå til localhost:8080 for å sette opp Wordpress. Når det er gjort, kan du opprette noen dummy-innlegg og et par Wordpress-sider i Wordpress-editoren, så du har noe å teste med senere.

Legg til GraphQL-støtte i Wordpress

Gatsby baserer seg på plugins for å hente data fra ulike kilder. Den pluginen som har vært brukt tidligere – gatsby-source-wordpress – baserer seg på Wordpress' REST-API, men det har nå kommet en ny versjon med det foreløpige navnet gatsby-source-wordpress-experimental. Denne vil endre navn til gatsby-source-wordpress@v4 etter hvert.

Blant fordelene med versjon 4 er at den baserer seg på en GraphQL-plugin til Wordpress. En av fordelene med å bruke GraphQL fremfor REST er at Wordpress vil returnere kun det du spør om – ikke alt mulig annet.

GraphQL-støtte er ikke innebygget i Wordpress, og du må derfor installere to plugins. Gå til admin-siden i Wordpress og så til Utvidelser. Installer så WPGraphQL og WPGatsby (du må laste dem ned som .zip-filer og så laste dem opp til Wordpress, deretter aktivere dem).

image: Bruk Wordpress som headless CMS, med Kurts Gatsby-løsning

Installer Gatsby

Du må nå installere kommandolinjeverktøyet til Gatsby, hvis du ikke allerede har det på PC-en din:

npm install -g gatsby-cli

Deretter går du til mappen wp-gatsby-testsite i starter-prosjektet du nettopp lastet ned fra Github. Kjør så denne kommandoen for å installere alle avhengighetene i prosjektet:

npm install

Tar du en kikk i package.json-filen ser du at den inneholder en rekke avhengigheter for blant annet bildehåndtering, tilgang til filsystemet, og selvfølgelig React.

Du har også en fil som heter gatsby-config.js hvor du definerer eventuelle plugins som skal brukes av Gatsby, i tillegg til metadata om nettstedet ditt. I starter-prosjektet mitt har jeg allerede installert gatsby-source-wordpress-experimental med npm install gatsby-source-wordpress-experimental og i tillegg har jeg lagt inn nødvendig konfigurasjon av denne pluginen i gatsby-config.js :

{
  resolve: `gatsby-source-wordpress-experimental`,
  options: {
    url: `http://localhost:8080/graphql`,
  },
},

Her må du oppdatere url så det peker til GraphQL-endepunktet til Wordpress-installasjonen din (som dukker opp etter at du har installert GraphQL-plugin for Wordpress, som beskrevet lenger opp).

Du kan nå starte opp Gatsby-nettstedet for å sjekke at det virker:

gatsby develop

Etter en liten stund får du forhåpentligvis beskjed om at byggeprosessen har vært vellykket – og du kan taste inn http://localhost:8000 i nettleseren.

Du skal da få opp en nettside som ser omtrent slik ut (med ditt innhold fra Wordpress):

image: Bruk Wordpress som headless CMS, med Kurts Gatsby-løsning

Litt grått og trist, kanskje – men et utmerket utgangspunkt for å bygge ditt eget nettsted, med responsiv design som skal være enkel å tilpasse. Forsiden har tre hovedelementer:

  • Navbar øverst på skjermen. Denne viser alle sider som er definert i Wordpress.
  • Tittel/beskrivelse. Her hentes tittel og beskrivelse fra Wordpress via GraphQL. Her kunne det selvfølgelig vært firmalogo eller hva som helst.
  • De seks siste innleggene fra Wordpress. Denne leveres av komponenten postPreviewGrid.js som du finner i mappen /src/components/

For at det skal være enkelt å tilpasse stylingen, har jeg brukt CSS Modules, og jeg har også brukt variabler for farger, fonter, «breakpoints» for media queries, osv. som du finner i mappen /src/styles.

Slik hentes sider og innlegg fra Wordpress

Innlegg (eller sider) hentes som sagt fra Wordpress ved hjelp av GraphQL-spørringer. Les om hvordan Gatsby bruker GraphQL her.

For eksempel henter jeg inn alle innleggene fra Wordpress (de som vises på forsiden) på denne måten i filen src/pages/index.js:

export const query = graphql`
  query allPosts {
    allWpPost(limit: 6, sort: { fields: date, order: DESC }) {
      edges {
        node {
          date(formatString: "DD. MMMM YYYY", locale: "NB-NO")
          slug
          uri
          title
          excerpt
          content
        }
      }
    }
  }
`

Resultatet (i JSON-format) sender jeg så som props til komponenten som viser utdrag av de seks første innleggene (src/components/postPreviewGrid.js) og itererer over hvert innlegg ved hjelp av .map:

import React from "react"
import { Link } from "gatsby"

import styles from "./postPreviewGrid.module.css"

const PostPreviewGrid = props => {
  return (
    <div className={styles.root}>
      <h4 className={styles.headline}>{props.title}</h4>
      <ul className={styles.container}>
        {props.nodes.edges.map(({ node }) => (
          <li key={node.slug} className={styles.listItem}>
            <Link to={node.uri}>
              <h2 className={styles.title}>{node.title}</h2>
            </Link>
            <div
              className={styles.excerpt}
              dangerouslySetInnerHTML={{ __html: node.excerpt }}
            />
            <Link to={node.uri} className={styles.excerpt}>
              Les mer...
            </Link>
            <p style={{ color: "#999" }}>Publisert: {node.date}</p>
          </li>
        ))}
      </ul>
    </div>
  )
}

export default PostPreviewGrid

Du kan bruke verktøyet GraphiQL for å lage spørringene og for å lete deg frem til dataene du vil hente ut – og så kopiere spørringene inn i koden din når du har noe som fungerer. Etter at du har startet opp Gatsby-nettstedet – i vårt tilfelle på port 8080, finner du GraphiQL ved å taste inn localhost:8080/__graphiql.

For eksempel ønsket jeg å hente ut tittel og beskrivelse av nettstedet fra Wordpress og vise dette på forsiden til Gatsby-nettstedet. GraphiQL har en Explorer-funksjon (til venstre på bildet) som gjør det lettere å finne det du leter etter. Trykk «play»-knappen for å kjøre spørringen og sjekke at den returnerer det du ønsker før du kopierer spørringen inn i koden din.

image: Bruk Wordpress som headless CMS, med Kurts Gatsby-løsning

Lage unike Gatsby-sider for hvert innlegg og hver side i Wordpress

Det å hente ut og vise Wordpress-innlegg på forsiden er vel og bra – men man vil jo gjerne også at hvert innlegg skal lede til en unik nettside når brukeren klikker på innlegget. Det er her filen gatsby-node.js kommer inn i bildet.

Denne filen kjøres én gang når nettsiden bygges, og ved hjelp av Gatsbys createPages-API kan du få Gatsby til å opprette sider for hvert av innleggene og sidene i Wordpress.

Du må først lage en GraphQL-spørring for å hente ut alle sider og innlegg, og deretter bruke createPages for hvert innlegg/side. CreatePages-funksjonen tar som argument et objekt som inneholder URL-en til nettsiden (den får den fra Wordpress), ID til det aktuelle Wordpress-innlegget, samt lenke til en mal for siden som skal opprettes.

Malen er en helt vanlig React-komponent som returnerer det du vil at siden skal vise. Den inneholder en GraphQL-spørring som ber om Wordpress-inlegget/siden med den samme ID-en som createPages refererer til i gatsby-node.js-filen.

Slik ser gatsby-node.js ut:

const path = require(`path`)
const { slash } = require(`gatsby-core-utils`)

exports.createPages = async ({ graphql, actions }) => {
  const { createPage } = actions

  // query content for WordPress posts
  const {
    data: {
      allWpPost: { nodes: allPosts },
      allWpPage: { nodes: allPages },
    },
  } = await graphql(`
    query {
      allWpPost {
        nodes {
          id
          uri
        }
      }
      allWpPage {
        nodes {
          id
          uri
        }
      }
    }
  `)

  const postTemplate = path.resolve(`./src/templates/post.js`)
  const pageTemplate = path.resolve(`./src/templates/page.js`)

  allPosts.forEach(post => {
    createPage({
      // will be the url for the page
      path: post.uri,
      // specify the component template of your choice
      component: slash(postTemplate),
      // In the ^template's GraphQL query, 'id' will be available
      // as a GraphQL variable to query for this post's data.
      context: {
        id: post.id,
      },
    })
  })

  allPages.forEach(page => {
    createPage({
      path: page.uri,
      component: slash(pageTemplate),
      context: {
        id: page.id,
      },
    })
  })
}

Og slik ser post.js-malen ut (page.js er omtrent lik):

// Template used for programmatically creating posts from data fetched from Wordpress
// Used by the createPage function in gatsby-node.js

import React from "react"
import { graphql } from "gatsby"

import Layout from "../components/layout"
import styles from "./post.module.css"

const postTemplate = ({ data }) => {
  const { page } = data
  const { title, content } = page
  return (
    <Layout>
      <div className={styles.postContainer}>
        <article>
          <h4 className={styles.blogTitle}>{title}</h4>
          <div
            className={styles.blogText}
            dangerouslySetInnerHTML={{ __html: content }}
          />
        </article>
      </div>
    </Layout>
  )
}
export default postTemplate

// The $id comes from the createPage function in gatsby-node.js
// Query the post with this ID, and use it in this template
export const query = graphql`
  query post($id: String!) {
    page: wpPost(id: { eq: $id }) {
      title
      content
    }
  }
`

For å sjekke hvilke sider createPages har opprettet, kan du prøve å gå til en side som ikke eksisterer. Når du er i utviklermodus (gatsby develop) vil alle genererte sider vises i stedet for den normale 404-siden. Tast inn for eksempel noe sånt som localhost:8000/sdf i nettleseren.

Alt i alt er det ikke voldsomt mye kode som skal til før du har et grunnleggende «skjelett» du kan bygge videre på. Ta en kikk på den offisielle dokumentasjonen til gatsby-source-wordpress@v4 beta (den som også kalles -experimental). Dokumentasjonen er ikke helt komplett ennå, men du finner en del eksempler og nyttige tips.

Lenke til starterprosjektet mitt: github.com/klekanger/wordpress-gatsby-starter