Lancer duolab.com en quelques mois avec Contentful et Nuxt.js — Retour d’expérience JAMstack Paris —7 Avril 2020 Yu Ling Cheng @yulingec
A presentation at Meetup JAMStack Paris in April 2020 in by Yu Ling Cheng
Lancer duolab.com en quelques mois avec Contentful et Nuxt.js — Retour d’expérience JAMstack Paris —7 Avril 2020 Yu Ling Cheng @yulingec
Yu Ling Cheng Lead développeur à Theodo #DevUx forever (devux.tech) Manque de soleil Plus de sport que d’habitude Follow me @yulingec
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
@yulingec
@yulingec
Diagnostique de peau @yulingec
Recommandation de produits @yulingec
Store e-commerce @yulingec
Contenu de présentation de la marque @yulingec
Frontend utilisateurs contenu CMS Headless contributeurs @yulingec
@yulingec
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
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
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
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
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
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
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
Fiche Modèles de données @yulingec
Fiche Modèles de données @yulingec
Fiche Modèles de données @yulingec
Fiche Modèles de données @yulingec
Fiche Modèles de données @yulingec
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
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
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
Modèles de données @yulingec
Modèles de données @yulingec
Modèles de données @yulingec
Modèles de données @yulingec
Page de contenu Modèles de données @yulingec
Page de contenu Modèles de données @yulingec
Page de contenu Modèles de données @yulingec
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
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
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
Modèles de données @yulingec
Modèles de données @yulingec
Bloc de contenu Modèles de données @yulingec
Bloc de contenu Modèles de données @yulingec
Bloc de contenu Modèles de données @yulingec
Bloc de contenu Modèles de données @yulingec
Bloc de contenu Modèles de données @yulingec
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
Modèles de données @yulingec
Modèles de données @yulingec
Traductions Modèles de données @yulingec
Traductions Modèles de données @yulingec
Traductions Modèles de données @yulingec
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
Modèles de données Fiche Page de contenu Bloc de contenu Traduction Modèles de données @yulingec
Exemple de modèle avancé Quiz https://www.duolab.com/gb/diagnosis Modèles de données @yulingec
Exemple de modèle avancé : Quiz Step 1: Définir la logique du quiz Modèles de données @yulingec
Exemple de modèle avancé : Quiz Step 2 : 2 modèles : Question et Réponse Modèles de données @yulingec
Exemple de modèle avancé : Quiz Step 2 : 2 modèles : Question et Réponse Modèles de données @yulingec
Exemple de modèle avancé : Quiz Step 3 : Remplir le contenu Modèles de données @yulingec
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
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
Retour d’expérience Modèles de données Intégration de services Developer Experience @yulingec
Frontend contenu CMS Headless Intégration de services @yulingec
Frontend contenu CMS Headless Intégration de services @yulingec
Frontend génération Moteur de recommandations contenu CMS Headless Intégration de services @yulingec
Frontend génération Moteur de recommandations contenu CMS Headless mapping des produits Intégration de services @yulingec
Frontend redirection Store e-commerce contenu CMS Headless Intégration de services @yulingec
Frontend redirection Store e-commerce contenu CMS Headless mapping des produits Intégration de services @yulingec
Mapping de données Intégration de services @yulingec
Mapping de données Intégration de services @yulingec
Mapping de données Intégration de services @yulingec
Mapping de données Intégration de services @yulingec
Mapping de données Intégration de services @yulingec
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
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
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
Retour d’expérience Modèles de données Intégration de services Developer Experience @yulingec
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
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
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
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
Retour d’expérience VS Directus (on-premise et open-source) UX/CX Célérité Maintenabilité Prix @yulingec
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