Week 5

De laatste hand aan de preview omgeving, het toevoegen van een 404 pagina

Deze week heb ik, waar m'n teamgenoten zich bezig hielden met testen, bezig gehouden het leggen van de laatste hand aan de preview omgeving.

Wanneer een content-editor een nieuwe pagina aanmaakte moest hij of zij eerst weer een nieuwe build draaien van de preview omgeving zodat het de laatste data gebruikt werd. Naast dit kreeg de gebruiker ook helemaal geen feedback maar simpelweg een 404 pagina (standaard van Vercel) te zien... Dat kan natuurlijk een stuk beter.

Custom 404 pagina bouwen

Het eerste wat ik heb gedaan om dit probleem op te lossen is het aanmaken van een custom 404 pagina zodat we de content op die pagina in eigen hand hebben. Dit maakt niet alleen dat die pagina beter aansluit bij onze huisstijl, het zorgt er ook voor dat we in de preview omgeving iets nuttigs hebben kunnen doen, waarover later meer.

Pretty simple

Het aanmaken van een custom 404 pagina ging best eenvoudig. Het enige wat ik heb hoeven doen was het toevoegen van een 404.html:

---
layout: "default"
title: "CMD Amsterdam | 404"
permalink: "404.html"
---

<div class="container">
  <h1 class="not-found__title">Oops, deze pagina bestaat niet 😭</h1>
  <p>Deze pagina bestaat helaas niet, ik zou <a href="/">terug gaan naar de homepage</a>.</p>
</div>

Dit maakte dat je op de website op een niet bestaande pagina dit kreeg te zien in plaats van een standaard 404 pagina van Vercel:

De custom 404 pagina

Nuttig implementeren in de preview omgeving

Echter, wanneer er een nieuwe pagina werd aangemaakt in de preview omgeving en je zou, zonder de boel te updaten, naar die pagina gaan dan krijg je alsnog te zien dat die pagina niet bestaat, terwijl een gebruiker die net heeft aangemaakt. Dat kan natuurlijk tot verwarring leiden...

Daarom heb ik in de preview omgeving een iets andere implementatie gemaakt van de 404 pagina. Ik heb daar namelijk gecheckt of iemand in de preview omgeving zit, als dat het geval is krijgt een gebruiker nuttigere feedback te zien:

---
layout: "default"
title: "CMD Amsterdam | 404"
permalink: "404.html"
---

{% set isInPreview = site.preview and site.preview.on %}

{% if isInPreview %}
<div class="container">
  <h1 class="not-found__title">Je pagina moet gebouwd worden, druk op save om dat proces te starten</h1>
  <p>Nadat je op save hebt gedrukt zal deze pagina na ongeveer 30 seconden verversen.</p>
</div>
{% else %}
<div class="container">
  <h1 class="not-found__title">Oops, deze pagina bestaat niet 😭</h1>
  <p>Deze pagina bestaat helaas niet, ik zou <a href="/">terug gaan naar de homepage</a>.</p>
</div>
{% endif %}

De pagina komt er dan in de preview als volgt uit te zien:

De 404 pagina in de preview omgeving geeft veel meer feedback

Door hier duidelijk aan de gebruiker te laten weten wat er aan de hand is en wat hij of zij moet doen om dit te fixen heeft de gebruiker er veel meer aan dan aan de standaard 404 pagina. Zoals te zien is in het screenshot moet de gebruiker op save drukken om het proces in werking te zetten, daarover ga ik nu meer vertellen.

Een headless preview omgeving laten updaten op 'save'

Om nog wat cognitieve last weg te halen bij een gebruiker en het minder omslachtig te maken om de preview omgeving te updaten heb ik ervoor gezorgd dat het wordt geüpdatet wanneer de gebruiker op 'save' drukt in het CMS.

Uit de eerder opgezette logica van de preview omgeving wisten we namelijk al dat we naar dat soort dingen kunnen luisteren events kunnen luisteren. Daarnaast hebben we ook al de webhook voor het updaten van de preview omgeving.

Webhook gebruiken voor het deployen van de preview omgeving

Zoals eerder uitgelegd is een webhook dus niet meer dan een endpoint waar naar we een verzoek kunnen doen, dus ook vanuit JavaScript. Het enige wat ons dan nog rest is de gebruiker feedback geven dat de preview geüpdatet wordt na ongeveer 30 seconden.

Ook moeten we zorgen dat elke volgende 'save' even niet opgevangen wordt zodat je niet ineens 10.000 verzoeken gaat doen naar de webhook aangezien onze deployment omgeving dan van slag raakt.

Om het helemaal 'veilig' te maken heb ik ervoor gezorgd dat we de webhook URL's verstoppen in environment variabelen. Vervolgens heb ik twee serverless functies geschreven omdat we daarin environment variabelen kunnen gebruiken:

  1. Deploy preview omgeving (op save)

  2. Deploy productie omgeving (bij publiceren of onpubliceren)

De serverless functie voor het deployen van de preview omgeving ziet er als volgt uit:

// /api/deploy-preview.js  
const fetch = require('node-fetch')
require('dotenv-safe').config()

const { PREVIEW_DEPLOY_HOOK } = process.env

module.exports = (request, response) => {
  fetch(PREVIEW_DEPLOY_HOOK)
    .then(() => {
      response.status(204)
      response.json({
        state: 'success',
        message: 'Deployed preview'
      })
    })
    .catch(error => {
      const statusCode = error.response.status || 500

      response.status(statusCode)
      response.json({
        state: 'error',
        message: 'Failed to deploy preview',
        error
      })
    })
}

Vervolgens kunnen we in de client-side code voor de preview een functie schrijven die weer een verzoek doet naar onze serverless functie om de preview omgeving te deployen:

function deployPreview() {
  return fetch('/api/deploy-preview')
    .then(() => {
      setTimeout(() => {
        location.reload()
      }, 30000)
    })
    .catch(error => {
      console.error('Failed to deploy preview: ', error)
    })
}

Feedback geven aan de gebruiker

Na 30 seconden wordt de pagina dus gerefresht als alles goed gaat. Echter, eigenlijk willen we de gebruiker nog wat extra feedback geven dat hun 'save' actie is opgevangen. Dit doen we door een soort dialoog element te tonen wat ik in de markup heb gezet als de gebruiker in de editor zit:

if (storyblok.isInEditor()) {
  const dialogContent = `
    <p class="preview-dialog visually-hidden"></p>
  `

  document.body.insertAdjacentHTML('afterbegin', dialogContent)
  
  // [...]
}
// [...]

function showDialog(text) {
  const dialogElement = document.getElementsByClassName('preview-dialog')[0]

  dialogElement.classList.remove('visually-hidden')
  dialogElement.textContent = text
}

Die showDialog functie gebruiken we vervolgens weer in de deployPreview functie:

function deployPreview() {
  showDialog('Je wijzigingen worden verwerkt... Het duurt 30 seconden voordat de pagina wordt ververst.')

  return fetch('/api/deploy-preview')
    .then(() => {
      setTimeout(() => {
        location.reload()
      }, 30000)
    })
    .catch(error => {
      console.error('Failed to deploy preview: ', error)
    })
}

Throttelen van meerdere keren klikken op save

Als een gebruiker achter elkaar op save zou drukken zou dit betekenen dat er elke seconde zowat een verzoek uitgaat naar de webhook. Hier wordt Vercel op den duur helemaal gek van en dan kunnen we helemaal niets meer deployen tijdelijk, niet bepaald iets wat we willen.

Om dat op te lossen heb ik ervoor gezorgd dat het verzoek gethrottled wordt met 30 seconden. Wanneer een gebruiker eenmaal op save heeft gedrukt wordt er 30 seconden niet meer naar de input geluisterd, totdat de website dus opnieuw gebuild is:

// src/assets/js/preview.js

import throttle from 'lodash.throttle'

if (storyblok.isInEditor()) {
    const thirtySeconds = 30000
    const throttledDeployPreview = throttle(
        deployPreview,
        thirtySeconds,
        { trailing: false }
    )
    
    storyblok.on('change', throttledDeployPreview)
}

Last updated

Was this helpful?