A presentation at Meetup JAMStack Paris 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
Le groupe l’Occitane vient de lancer Duolab, sa nouvelle marque premium qui propose un soin visage personnalisé innovant. L’enjeu pour l’équipe tech de Duolab était de créer une expérience en ligne sur mesure et qui colle à l’image de la marque, tout en laissant la main aux éditeurs sur le contenu. C’était une opportunité parfaite pour utiliser un CMS headless! Pour ma première expérience sur un projet de grande ampleur avec Contentful, j’ai trouvé cette expérience extrèmement enrichissante. Je partagerai avec vous les types de modèles qui nous ont le plus servi, comment on mis en place une boutique en ligne rapidement avec Shopify headless commerce, et les difficultés et apprentissages que j’en ai tirés.
The following resources were mentioned during the presentation or are useful additional information.
Le site de Duolab
Quiz dont le contenu et la logique sont administrables via Contenful
Here’s what was said about this presentation on social media.
Merci @jamstackparis d’avoir organisé une édition en visio pour faire vivre le meetup pendant le confinement 🚀 J’y partage un REX sur l’utilisation de Contentful sur Duolab, une nouvelle marque de L’Occitane #headlessCMS https://t.co/V4YOO7xqBF
— Yu Ling Cheng (@YuLingEC) April 23, 2020