AccueilClients

Applications et sites

  • Application métiersIntranet, back-office...
  • Applications mobilesAndroid & iOS
  • Sites InternetSites marketings et vitrines
  • Expertises techniques

  • React
  • Expo / React Native
  • Next.js
  • Node.js
  • Directus
  • TypeScript
  • Open SourceBlogContactEstimer

    10 août 2022

    Création d'une application e-commerce avec Remix

    8 minutes de lecture

    Création d'une application e-commerce avec Remix

    Introduction

    Nous avons jusqu’ici utilisé plusieurs outils pour développer nos applications front :

    • Au début, nous utilisions Webpack et son dev server, Babel et React, une stack assez lourde à maintenir et difficile à appréhender pour les non initiés au config des bundles ;
    • Progressivement s'est imposé Create React App, un toolkit permettant d'abstraire toutes ces librairies avec une config générique couvrant la majorité des cas ;
    • Aujourd'hui, nous avons remplacé CRA par Next.js. Ce framework diffère de CRA par son approche fullstack mais peut être utilisé en tant qu'application statique.

    Grâce à son approche SSR, il nous est facile de répondre à certaines problématiques et d'utiliser des handler d'appoint côté Node (appeler une database grâce à prisma par exemple), même si nous privilégions dans le cas d'API des frameworks comme Nest.js. Son API permettant de générer des pages statiques et de les invalider à la demande est très pratique.

    Depuis peu, un nouveau framework est apparu dans l'écosystème React : Remix. Ce dernier diffère des autres outils, du fait qu'il soit uniquement server side. On peut donc parler plus d'un framework Web, avec une intégration de premier plan avec React.

    On vous propose ici de découvrir Remix via un cas concret de développement de site e-commerce.

    L'ensemble du code présenté ici est disponible en open source sur Github.

    C'est un projet d'exemple avec certains choix qui peuvent être spécifique au contexte du site que nous avons voulu développer. C'est cependant une base fonctionnelle pour un petit site e-commerce.

    Remix shop

    Remix

    Standards Web

    Construit au plus proche de la spécification HTTP, Remix n’invente pas de nouveauté mais construit un socle Node autour des standards avec une DX importante.

    On retrouve rapidement les différentes méthodes HTTP directement dans le code via des fonctions spécifiques (GET = function loader, POST/PUT/DELETE = function action), ainsi que les interfaces de la Fetch API comme Request et Response.

    Ce confort nécessite toutefois l’utilisation d’adapter selon l’endroit où l’on souhaite déployer. Remix a été développé avec plusieurs adaptateurs “edge” comme Cloudflare ou Vercel, mais peut très bien être utilisé de manière plus classique avec un serveur Express.js.

    Retour vers le futur

    Venant d’un background plutôt backend, j’ai souvent été confronté à des changements de paradigmes en travaillant de plus en plus avec des technologies front (déplacement de logique côté client, appel API au lieu d’appel SQL, programmation POO VS fonctionnelle).

    C’est avec plaisir que j’ai retrouvé plus de simplicité via un retour aux sources et un projet dont la majeure partie du code tourne sur le serveur.

    Remix est à mon sens un framework back pour le front, par exemple la gestion d’un formulaire va utiliser tout ce qui est déjà présent dans la spec HTTP, avec une requête POST et des champs inputs, nous allons pouvoir par la suite ajouter une couche d’amélioration progressive au niveau de l’UI, pour par exemple gérer des loaders ou de la validation en temps réel. On peut d’ailleurs trouver sur la documentation officielle des indications pour désactiver JavaScript côté client.

    Création d’un site e-commerce

    Devant réaliser un petit site e-commerce avec beaucoup de liberté, j’en ai profité pour expérimenter sur ce jeune framework plein de promesses.

    Contexte

    Fonctionnalités

    La liste des fonctionnalités est assez classique pour ce projet e-commerce avec

    • Une page produit avec sélecteur de variante, slider de photo
    • Page catégorie, liste de produits triés
    • Pages CMS, page de contenu dynamique administrable
    • Panier
    • Paiement en ligne
    • Interface d’administration
    • Flow de commande simple (paiement, email de commande, envoi de colis)

    Architecture

    Framework : Le sujet de cet article et ce projet, Remix est le socle du site web

    Hébergement : assuré par Vercel, un hébergeur orienté développeur et offrant un déploiement rapide, avec des previews par branche.

    Paiement : Stripe propose une plateforme de paiements sécurisés avec de nombreux composants clés en main configurable

    Données : Dans un souci de rapidité et simplicité, j’ai choisi le CMS en ligne GraphCMS. Ce point mériterait presque un article à part entière, j’ai apprécié la vélocité apportée par l’outil et la puissance des fonctionnalités (administration extensible, gestion poussée des médias, API GraphQL).

    Données et administration

    GraphCMS

    GraphCMS permet de définir un modèle de données directement sur l’interface web via du drag&drop de composants “Fields”.

    Hypergraph fields

    Ce modèle est automatiquement exposé via l’API GraphQL (en lecture) et un playground nous permet de développer et tester nos requêtes facilement.

    Côté administration des données on retrouve un formulaire généré automatiquement (mais configurable) pour créer la donnée

    Hypergraph editor

    GraphQL

    Que les données soient exposées via une API REST avec un Swagger ou GraphQL, nous avons pour habitude de générer automatiquement un client d’API TypeScript via l’outil graphql-codegen.

    La configuration pour générer les types et requêtes API est la suivante :

    overwrite: true
    schema: 'https://api-eu-central-1.graphcms.com/v2/XXX/master'
    documents: './app/graphql/*.(ts|tsx)'
    watch: true
    generates:
      app/graphql/generated/graphql.ts:
        plugins:
          - 'typescript'
          - 'typescript-operations'
          - 'typescript-graphql-request'
    

    Il reste ensuite à créer notre client d’API

    import { GraphQLClient } from 'graphql-request'
    import { getSdk } from '~/graphql/generated/graphql'
    
    const client = new GraphQLClient('https://api-eu-central-1.graphcms.com/v2/XXX/master', {
      fetch: fetch,
    })
    const sdk = getSdk(client)
    
    export default sdk
    

    Et “voilà” !

    Habituellement nous utilisons aussi la génération de hooks react-query, mais pour ce projet l’ensemble des requêtes sont effectuées côté serveur.

    Page produit

    Sur une route (ou page) Remix, l’export par défaut est le composant que l’on veut rendre. Il est possible d’ajouter des comportements via d’autres fonctions. Pour cette page il va être nécessaire de faire une requête d’API pour récupérer les données du produit, d’exposer des métadonnées HTML, et d’inclure des styles de librairies tierces (Swiper.js).

    La fonction loader reçoit en paramètres la requête ainsi que son contexte, on peut notamment y retrouver les paramètres de notre page, comme le slug pour notre cas. Ainsi on peut faire la requête vers notre API et retourner les données à notre vue (le composant exporté dans le même fichier). À noter que nous pourrions très bien ici faire une requête SQL directement sur une base de données, ce code n’est exécuté que sur le serveur !

    export let loader: LoaderFunction = async ({ params }) => {
      const { product } = await api.getProduct({ slug: params.slug! })
    
      if (!product) {
        throw new Response('Not Found', {
          status: 404,
        })
      }
    
      return json(product)
    }
    

    Le fonctionnement est similaire pour les metas et l’ajout de CSS pour le cas du slider, sauf que l'on va exposer la fonction links. A noter que seul cette page possède un slider, et seul cette page chargera le CSS et le JS dont ce dernier à besoin grâce à la séparation des bundles par route. Aussi, on peut utiliser les données exposées par le loader car elles sont directement passées à la fonction meta pour nous permettre d’enrichir les métadonnées de la page dynamiquement.

    export const links = () => {
     return [
       { rel: "stylesheet", href: swiperCss },
     ];
    };
    
    export const meta: MetaFunction = ({ data }: { data: Product }) => {
     return {
       "og:type": "og:product",
       title: data.name,
     };
    };
    

    Les pages CMS sont construites avec le même principe mais avec un système très basique de modification du layout et du contenu HTML donc elles ne seront pas plus détaillées.

    <Box className="wysiwyg" dangerouslySetInnerHTML={{ __html: page?.content.html! }} />
    

    Formulaires

    La récupération des données dans Remix passe par l’API Loader, pour l’écriture c’est assez similaire avec l’API Action.

    Prenons l’exemple d’une page qui ferait un POST sur elle-même (formulaire de contact par exemple).

    export const action: ActionFunction = async ({ request }) => {
      const body = await request.formData()
      // TODO Do something with form data
      return new Response(undefined, { status: 204 })
    }
    

    Une fois que le comportement de sauvegarde avec un formulaire HTML classique fonctionne, il est possible de l’améliorer progressivement via l’utilisation d’un composant Form et du hook useTransition.

     const transition = useTransition();
    
     return (
         <Form method="post">
           <FormControl
             as="fieldset"
             disabled={transition.state === "submitting"}
           >
             ...
             <Button
               leftIcon={<EmailIcon />}
               isLoading={transition.state === "submitting"}
             >
               Envoyer
             </Button>
           </FormControl>
     );
    

    Un exemple plus complet avec l’état du formulaire ainsi que de la validation se trouve sur la documentation officielle.

    Gestion du panier et stock

    Le panier doit permettre d’ajouter, supprimer ou modifier les produits. Il doit aussi être persistant. Pour simplifier au maximum le code du projet j’ai préféré utiliser un hook qui gère l’ensemble de ces fonctions : react-use-cart.

    Le stock est exposé et permet d’avoir une visualisation en temps réel lors de l’ajout au panier. Le panier est ensuite validé côté serveur lors du paiement Stripe.

    export const action: ActionFunction = async ({ request }) => {
      try {
        const cartProducts: ProductCart[] = await request.json();
        // Get products with the API, check stock and throw otherwise
        const products = await formatProducts(cartProducts);
    

    Webhooks

    L’utilisation de services tiers pour le paiement et les données impact l’architecture du projet, le tunnel de commande clé en main de Stripe fait gagner un temps précieux mais nécessite cependant de gérer la synchronisation de données.

    Il est nécessaire d’utiliser les webhooks pour plusieurs fonctionnalités :

    • Stripe ➡️ e-commerce : création d’une commande après un paiement avec succès. Le SDK Stripe
    • GraphCMS ➡️ e-commerce :
      • envoi d’email de validation de commande au client
      • envoi d’email d’envoi de commande avec lien de suivi (modification directement dans l’administration)

    Conclusion

    La recherche de simplicité sur ce projet a orienté fortement le choix de l’utilisation de Remix. L'approche serveur m'a évité de devoir utiliser des packages pour gérer mes requêtes ou une architecture front (react-query, Context par exemple), tout en pouvant ajouter des comportements avancées côté front (slider, localStorage).

    De plus, on ressent un important effort de documentation de la part des développeurs du framework, avec des exemples d'applications complètes et même plusieurs stacks de démarrage avec différentes bases de données ou authentification.

    Pour finir, je partage totalement le ressenti de Mark Dalgleish sur le retour à la simplicité d’un framework serveur qui permet de garder une UX augmentée si nécessaire.

    J'ai pour ma part hâte de créer de nouvelles applications avec ce framework !