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

par

Thibault

Thibault Lenclos

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 !

Continuer la discussion sur Twitter

18 avenue Parmentier
75011 Paris
+33 1 43 57 39 11
hello@premieroctet.com

Suivez nos aventures

GitHub

Naviguez à vue