Lancer duolab.com en quelques mois avec Contentful et Nuxt.js—Retour d’expérience

A presentation at Meetup JAMStack Paris in April 2020 in by Yu Ling Cheng

Slide 1

Slide 1

Lancer duolab.com en quelques mois avec Contentful et Nuxt.js — Retour d’expérience JAMstack Paris —7 Avril 2020 Yu Ling Cheng @yulingec

Slide 2

Slide 2

Yu Ling Cheng Lead développeur à Theodo #DevUx forever (devux.tech) Manque de soleil Plus de sport que d’habitude Follow me @yulingec

Slide 3

Slide 3

Duolab, c’est quoi ? Une nouvelle marque de L’Occitane Un soin sur mesure pour le visage Le fruit de 6 ans d’innovations en laboratoire Lancé le 6 février dans un Pop-up Store à Londres @yulingec

Slide 4

Slide 4

@yulingec

Slide 5

Slide 5

@yulingec

Slide 6

Slide 6

Diagnostique de peau @yulingec

Slide 7

Slide 7

Recommandation de produits @yulingec

Slide 8

Slide 8

Store e-commerce @yulingec

Slide 9

Slide 9

Contenu de présentation de la marque @yulingec

Slide 10

Slide 10

Frontend utilisateurs contenu CMS Headless contributeurs @yulingec

Slide 11

Slide 11

@yulingec

Slide 12

Slide 12

Retour d’expérience Enjeux UX/CX Célérité Maintenabilité Expérience Utilisateurs & Expérience Contributeurs Rapidité de mise en place Expérience de développement @yulingec

Slide 13

Slide 13

Flexibilité Expérience Utilisateurs & Expérience Contributeurs Célérité Maintenabilité place développement Retour d’expérience Rapidité de mise en Expérience de Focus Modèles de données Intégration de services Developer Experience @yulingec

Slide 14

Slide 14

Modèle Produit Nom : Texte Description : Texte long Images : Liste de fichier au format image Produits associés : Liste de produits Modèles de données @yulingec

Slide 15

Slide 15

Modèle Contenu Produit Produit Produit Produit Nom : Texte Nom : Base de jour light Description : Description : Texte long Blabla Bla bla bla, bla bla blabla blablabla Images : Liste de fichier au format image Images : Produits associés : Liste de produits Produits associés : Modèles de données Base de nuit @yulingec

Slide 16

Slide 16

Modèle Contenu Produi Produi tt Produit Produit Nom : Texte Nom : Base de jour light Description : Description : Texte long Bla bla bla, bla bla blabla blablabla Blabla Images : Liste de fichier au format image Images : Produits associés : Liste de produits Produits associés : Modèles de données API Base de nuit @yulingec

Slide 17

Slide 17

Modèle Contenu Produi Produi tt Produit Produit Nom : Texte Nom : Base de jour light Description : Description : Texte long Bla bla bla, bla bla blabla blablabla Blabla Images : Liste de fichier au format image Images : Produits associés : Liste de produits Produits associés : Modèles de données API Base de nuit @yulingec

Slide 18

Slide 18

Modèle Contenu Produi Produi tt Produit Produit Nom : Texte Nom : Base de jour light Description : Description : Texte long Bla bla bla, bla bla blabla blablabla Blabla Images : Liste de fichier au format image Images : Produits associés : Liste de produits Produits associés : Modèles de données API Base de nuit @yulingec

Slide 19

Slide 19

Fiche Modèles de données @yulingec

Slide 20

Slide 20

Fiche Modèles de données @yulingec

Slide 21

Slide 21

Fiche Modèles de données @yulingec

Slide 22

Slide 22

Fiche Modèles de données @yulingec

Slide 23

Slide 23

Fiche Modèles de données @yulingec

Slide 24

Slide 24

Fiche Tips : • Bien comprendre les attentes des contributeurs pour définir les types des champs et relations • Prendre le temps de définir les helpers pour aider les contributeurs à bien remplir le contenu • Configurer une preview pour faciliter la contribution Modèles de données @yulingec

Slide 25

Slide 25

Fiche Tips : • Bien comprendre les attentes des contributeurs pour définir les types des champs et relations • Prendre le temps de définir les helpers pour aider les contributeurs à bien remplir le contenu • Configurer une preview pour faciliter la contribution Modèles de données @yulingec

Slide 26

Slide 26

Fiche Tips : • Bien comprendre les attentes des contributeurs pour définir les types des champs et relations • Prendre le temps de définir les helpers pour aider les contributeurs à bien remplir le contenu • Configurer une preview pour faciliter la contribution UX/CX Modèles de données Célérité Maintenabilité @yulingec

Slide 27

Slide 27

Modèles de données @yulingec

Slide 28

Slide 28

Modèles de données @yulingec

Slide 29

Slide 29

Modèles de données @yulingec

Slide 30

Slide 30

Modèles de données @yulingec

Slide 31

Slide 31

Page de contenu Modèles de données @yulingec

Slide 32

Slide 32

Page de contenu Modèles de données @yulingec

Slide 33

Slide 33

Page de contenu Modèles de données @yulingec

Slide 34

Slide 34

Page de contenu Avantage : Un seul endroit pour contrôler le contenu d’une page Inconvénients : • Structure peu flexible • Multiplication de modèles à entrée unique Modèles de données @yulingec

Slide 35

Slide 35

Page de contenu Avantage : Un seul endroit pour contrôler le contenu d’une page Inconvénients : • Structure peu flexible • Multiplication de modèles à entrée unique Modèles de données @yulingec

Slide 36

Slide 36

Page de contenu Avantage : Un seul endroit pour contrôler le contenu d’une page Inconvénients : • Structure peu flexible • Multiplication de modèles à entrée unique UX/CX Modèles de données Célérité Maintenabilité @yulingec

Slide 37

Slide 37

Modèles de données @yulingec

Slide 38

Slide 38

Modèles de données @yulingec

Slide 39

Slide 39

Bloc de contenu Modèles de données @yulingec

Slide 40

Slide 40

Bloc de contenu Modèles de données @yulingec

Slide 41

Slide 41

Bloc de contenu Modèles de données @yulingec

Slide 42

Slide 42

Bloc de contenu Modèles de données @yulingec

Slide 43

Slide 43

Bloc de contenu Modèles de données @yulingec

Slide 44

Slide 44

Bloc de contenu Avantage : Flexibilité absolue Inconvénient : Expérience moins claire pour les contributeurs Variante : Gestion de listes (carrousel) UX/CX Modèles de données Célérité Maintenabilité @yulingec

Slide 45

Slide 45

Modèles de données @yulingec

Slide 46

Slide 46

Modèles de données @yulingec

Slide 47

Slide 47

Traductions Modèles de données @yulingec

Slide 48

Slide 48

Traductions Modèles de données @yulingec

Slide 49

Slide 49

Traductions Modèles de données @yulingec

Slide 50

Slide 50

Traductions Avantage : MVP fonctionnel rapidement Inconvénient : Expérience moins claire pour des traducteurs habitués à avoir du contexte UX/CX Modèles de données Célérité Maintenabilité @yulingec

Slide 51

Slide 51

Modèles de données Fiche Page de contenu Bloc de contenu Traduction Modèles de données @yulingec

Slide 52

Slide 52

Exemple de modèle avancé Quiz https://www.duolab.com/gb/diagnosis Modèles de données @yulingec

Slide 53

Slide 53

Exemple de modèle avancé : Quiz Step 1: Définir la logique du quiz Modèles de données @yulingec

Slide 54

Slide 54

Exemple de modèle avancé : Quiz Step 2 : 2 modèles : Question et Réponse Modèles de données @yulingec

Slide 55

Slide 55

Exemple de modèle avancé : Quiz Step 2 : 2 modèles : Question et Réponse Modèles de données @yulingec

Slide 56

Slide 56

Exemple de modèle avancé : Quiz Step 3 : Remplir le contenu Modèles de données @yulingec

Slide 57

Slide 57

Exemple de modèle avancé : Quiz Step 4 : Implémenter le front <script> import Vue from “vue”; import Question from “~/components/Quiz/Question”; import Answer from “~/components/Quiz/Answer”; import { getQuestions } from “~/services/quiz.js”; export default Vue.extend({ name: “Quiz”, components: { Question, Answer, }, asyncData({ app, error }) { return getQuestions(app, error); }, computed: { currentQuestion() { return this.$store.state.quiz.currentQuestion; }, }, }); </script> Modèles de données <template> <div class=”quiz-container”> <!—Display the current question —> <Question :question=”currentQuestion” /> <!-Display available answers. Choosing an answer will trigger the update of: - the score - the state currentQuestion —> <Answer :v-for=”answer in currentQuestion.answers” :key=”answer.id” :answer=”answer” /> </div> </template> @yulingec

Slide 58

Slide 58

Exemple de modèle avancé Quiz Step 1: Step 2 : Définir la logique du quiz 2 modèles : Question et Réponse Step 3 : Step 4 : Remplir le contenu Implémenter le front UX/CX Célérité Maintenabilité Autre exemple de quiz sur ce modèle : https://devux.tech/getting-started Modèles de données @yulingec

Slide 59

Slide 59

Retour d’expérience Modèles de données Intégration de services Developer Experience @yulingec

Slide 60

Slide 60

Frontend contenu CMS Headless Intégration de services @yulingec

Slide 61

Slide 61

Frontend contenu CMS Headless Intégration de services @yulingec

Slide 62

Slide 62

Frontend génération Moteur de recommandations contenu CMS Headless Intégration de services @yulingec

Slide 63

Slide 63

Frontend génération Moteur de recommandations contenu CMS Headless mapping des produits Intégration de services @yulingec

Slide 64

Slide 64

Frontend redirection Store e-commerce contenu CMS Headless Intégration de services @yulingec

Slide 65

Slide 65

Frontend redirection Store e-commerce contenu CMS Headless mapping des produits Intégration de services @yulingec

Slide 66

Slide 66

Mapping de données Intégration de services @yulingec

Slide 67

Slide 67

Mapping de données Intégration de services @yulingec

Slide 68

Slide 68

Mapping de données Intégration de services @yulingec

Slide 69

Slide 69

Mapping de données Intégration de services @yulingec

Slide 70

Slide 70

Mapping de données Intégration de services @yulingec

Slide 71

Slide 71

Mapping de données <!doctype html> <html lang=”en”> <head> <meta charset=”UTF-8”> <title>Products</title> <link rel=”stylesheet” href=”https://unpkg.com/contentfului-extensions-sdk@3/dist/cf-extension.css”> <script src=”https://unpkg.com/contentful-ui-extensionssdk@3”></script> <script src=”https://unpkg.com/jquery@3”></script> <style> .-warning { color: #d9453f; margin-top: 4px; display: none; } </style> </head> <body> <div class=”cf-form-field”> <select id=”products” class=”cf-form-input”> <option value=”-1” disabled>Select…</option> </select> <div class=”cf-form-hint”>Please select the desired product.</div> <div class=”cf-form-hint -warning” data-mkto-warning>⚠ Error in this extension.</div> <div class=”cf-form-hint -warning” data-lambda-warning>⚠ Error fetching data.</div> </div>

<script type=”text/javascript”> window.contentfulExtension.init(function(api) { api.window.startAutoResizer(); var value = api.field.getValue(); var selectField = $(“#products”); var forms = []; $.ajax({ url: ‘https://jsonplaceholder.typicode.com/users’, type: “GET”, crossDomain: true, dataType: “json”, success: function(response) { forms = response; forms.forEach(function(value, index) { var option = document.createElement(“option”); option.setAttribute(“value”, index); option.innerText = value.name; }); var value = api.field.getValue(); var id = value && value.id; var index = forms.findIndex(function(form) { return form.id === id; }); selectField.val(index); if (id && index < 0) { $(“[data-mkto-warning]”).show(); } <script type=« text/javascript »>/****/</script> </body> </html>

selectField.append(option); }); }, error: function(xhr, status) { $(“[data-lambda-warning]”).show(); console.log(“Error fetching data. Status:”, status); } selectField.on(“input”, function() { api.field.setValue(forms[this.value]); }); }); </script> Intégration de services @yulingec

Slide 72

Slide 72

Mapping de données <!doctype html> <html lang=”en”> <head> <meta charset=”UTF-8”> <title>Products</title> <link rel=”stylesheet” href=”https://unpkg.com/contentfului-extensions-sdk@3/dist/cf-extension.css”> <script src=”https://unpkg.com/contentful-ui-extensionssdk@3”></script> <script src=”https://unpkg.com/jquery@3”></script> <style> .-warning { color: #d9453f; margin-top: 4px; display: none; } </style> </head> <body> <div class=”cf-form-field”> <select id=”products” class=”cf-form-input”> <option value=”-1” disabled>Select…</option> </select> <div class=”cf-form-hint”>Please select the desired product.</div> <div class=”cf-form-hint -warning” data-mkto-warning>⚠ Error in this extension.</div> <div class=”cf-form-hint -warning” data-lambda-warning>⚠ Error fetching data.</div> </div>

<script type=”text/javascript”> window.contentfulExtension.init(function(api) { api.window.startAutoResizer(); var value = api.field.getValue(); var selectField = $(“#products”); var forms = []; $.ajax({ url: ‘https://jsonplaceholder.typicode.com/users’, type: “GET”, crossDomain: true, dataType: “json”, success: function(response) { forms = response; forms.forEach(function(value, index) { var option = document.createElement(“option”); option.setAttribute(“value”, index); option.innerText = value.name; }); var value = api.field.getValue(); var id = value && value.id; var index = forms.findIndex(function(form) { return form.id === id; }); selectField.val(index); if (id && index < 0) { $(“[data-mkto-warning]”).show(); } <script type=« text/javascript »>/****/</script> </body> </html>

selectField.append(option); }); }, error: function(xhr, status) { $(“[data-lambda-warning]”).show(); console.log(“Error fetching data. Status:”, status); } selectField.on(“input”, function() { api.field.setValue(forms[this.value]); }); }); </script> Intégration de services @yulingec

Slide 73

Slide 73

Mapping de données Avantage : Rapide à mettre en place Inconvénient : Difficile de configurer plusieurs environnements UX/CX Intégration de services Célérité Maintenabilité @yulingec

Slide 74

Slide 74

Retour d’expérience Modèles de données Intégration de services Developer Experience @yulingec

Slide 75

Slide 75

Récupération de la donnée /* nuxt.config.js / plugins: [ // … ‘~/plugins/contentful’, ], / plugins > contentful.js */ const contentful = require(‘contentful’) const contentfulClient = contentful.createClient({ space: ‘XXXXXXXXXXXXX’, accessToken: ‘XXXXXXXXXXXXX’ }) contentfulClient.getTranslationEntries = async (translationKeys) => { const translationEntries = await contentfulClient.getEntries({ content_type: ‘content’, ‘fields.key’: translationKeys, select: ‘fields.key,fields.value’ }) return parseTranslationEntries(translationEntries) } export const parseTranslationEntries = (translationEntries) => translationEntries.items.reduce((trans, translationEntry) => { if ( translationEntry.fields && translationEntry.fields.key && translationEntry.fields.value ) { return { …trans, [translationEntry.fields.key]: translationEntry.fields.value } } }, {}) export default (_context, inject) => { inject(‘cms’, contentfulClient) } Intégration de services

<!— pages > faq > index.vue —> <script> import FAQFooter from ‘~/components/Faq/FAQFooter’ export default { components: { FAQFooter }, layout: ‘page’, async asyncData({ app, error }) { try { // Get FAQ list from Contentful const faq = await app.$cms.getEntries({ content_type: ‘faq’, order: ‘fields.title’ }) const translations = await app.$cms.getTranslationEntries([ ‘faq.footer.see_more’, ‘faq.footer.ask_question’ ]) return { faqList: faq.items, translations } } catch (e) { // log error with Sentry app.$sentry.captureException(new Error(‘FAQ not found’)) error({ statusCode: 404, message: ‘FAQ not found’ }) } } } </script>

@yulingec

Slide 76

Slide 76

Récupération de la donnée Avantages : SDK qui facilite la récupération des données Inconvénients : Valider des données que l’on reçoit ? Erreurs liées au soft delete Célérité Intégration de services Maintenabilité @yulingec

Slide 77

Slide 77

Gestion des environnements Mono-Environnement : Possible mais limité dans le cas d’intégrations avec d’autres services Propice aux bugs…? Multi-Environnements : Possible mais fonctionnalité early stage Coût d’entrée pour gérer tout le développement avec des scripts de migration Célérité Intégration de services Maintenabilité @yulingec

Slide 78

Slide 78

Retour d’expérience Conclusion UX/CX Célérité Maintenabilité Interface simple pour gérer des modèles +/complexes Rapide à déployer (installation, SDK, extensions…) Point d’attention : Gestion des environnements @yulingec

Slide 79

Slide 79

Retour d’expérience VS Directus (on-premise et open-source) UX/CX Célérité Maintenabilité Prix @yulingec

Slide 80

Slide 80

Lancer duolab.com en quelques mois avec Contentful et Nuxt.js — Retour d’expérience JAMstack Paris 7 Avril 2020 Merci ! Yu Ling Cheng #DevUx forever (devux.tech) Follow me @yulingec