AccueilClients

Applications et sites

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

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

    2 septembre 2021

    Gestion de blocks CMS avec Strapi, Next.js et TypeScript

    6 minutes de lecture

    Gestion de blocks CMS avec Strapi, Next.js et TypeScript

    Nous vous présentions dans notre dernier article, le CMS headless Strapi que nous avions eu l'occasion d'utiliser dans le cadre d'un projet. Aujourd'hui nous allons rentrer plus en détails, avec une fonctionnalité très pratique de Strapi : les composants et les zones dynamiques. Ceux-ci vont nous permettre de créer un système de pages CMS avec une gestion de blocks flexible.

    Voici les différentes étapes dans la mise en place de notre système de CMS :

    Côté back (Strapi):

    • Création de la structure des blocks dans Strapi (ex: Cover, Slider, Latest posts) ;
    • Création d'un système de pages CMS dans Strapi.

    Coté front (Next.js)

    • Génération des types et client d'API TypeScript côté Next.js ;
    • Implémentation du routing CMS (génération statique) ;
    • Implémentation des blocks (avec TypeScript).

    Définition des pages et blocks côté Strapi

    Création de la structure des blocks

    Nous allons commencer par créer nos différents blocks de notre CMS, par blocks on entend des widgets que l'utilisateur pourra ajouter à une page du CMS. Pour cela nous allons utiliser les composants de Strapi. Ceux-ci permettent de créer des types contenant plusieurs champs (image, texte, relations…) pouvant être réutilisés facilement.

    Dans cet article nous allons créer deux composants :

    • Hero
    • Section

    Pour cela cliquer sur Content Types builder dans le menu latéral puis Composant > Créer un composant.

    Voici nos deux composants créés :

    Composant Hero

    Composant Section

    Ajout du système de pages CMS

    Ajoutons désormais notre gestion des pages dans Strapi. Pour cela nous allons créer une nouvelle collection de Page avec 3 champs :

    • title (texte)
    • description (texte)
    • slug (UID basé sur le title)

    Collection Page

    Enfin ajoutons un 4ème et dernier champ nommé blocks de type Dynamic Zone nous permettant d'ajouter nos blocks précédemment créés. Lors de la création de ce champ, nous devons définir les blocks qu'une Page peut afficher, dans notre cas les blocks Hero et Section :

    Block d'une page

    Il ne nous reste plus qu'à créer une page et y ajouter un ou plusieurs blocks :

    Une fois sauvegardée, notre page est accessible via l'API REST générée par Strapi sur le endpoint http://localhost:1337/pages :

    Pensez à autoriser le listing des pages (Paramètres > Roles & permissions > Public > Pages > find & findone)

    Notre page contient bien un tableau blocks avec nos blocks ajoutés dans Strapi :

    // http://localhost:1337/pages
    [
      {
        "id": 1,
        "title": "Notre restaurant",
        "description": "Découvrez notre restaurant",
        "slug": "decouvrez-le-projet",
        "blocks": [
          {
            "__component": "block.hero",
            "id": 1,
            "title": "Notre restaurant",
            "image": {
              "id": 1,
              "width": 934,
              "height": 1401,
              "url": "/uploads/photo_1520279406162_c955e67194ed_6469e0ca7e.webp"
            }
          }
        ]
      }
    ]
    

    Notre API est prête à être consommée par un front. Nous allons utiliser Next.js qui va nous permettre de générer statiquement nos pages CMS.

    Implémentation du CMS côté front

    Génération des types et client d’API en TypeScript

    Chez Premier Octet nous développons tous nos projets React en TypeScript, nous permettant d'avoir une base de code solide et à l'épreuve de régression.

    L'avantage de Strapi est d'exposer automatiquement une spécification Swagger exhaustive de l'API REST.

    Activer la documentation en allant dans le menu Marketplaces > Documentation > Télécharger

    En s'appuyant sur cette spécification, nous pouvons générer en une ligne de commande un client d'API Axios typé automatiquement grâce à la librairie swagger-typescript-api :

    swagger-typescript-api -p ../server/extensions/documentation/documentation/1.0.0/full_documentation.json -o ./typings -n api.ts --route-types --module-name-index 0 --axios
    

    Voici le type généré pour notre page :

    /* eslint-disable */
    /* tslint:disable */
    /*
     * ---------------------------------------------------------------
     * ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API        ##
     * ##                                                           ##
     * ## AUTHOR: acacode                                           ##
     * ## SOURCE: https://github.com/acacode/swagger-typescript-api ##
     * ---------------------------------------------------------------
     */
    
    export interface Page {
      id: string
      title: string
      description?: string
      slug: uid
      blocks?: (
        | {
            __component?: 'block.hero' | 'block.section'
            id: string
            title: string
            image?: {
              id: string
              name: string
              alternativeText?: string
              caption?: string
              width?: number
              height?: number
              formats?: object
              hash: string
              ext?: string
              mime: string
              size: number
              url: string
              previewUrl?: string
              provider: string
              provider_metadata?: object
              related?: string
              created_by?: string
              updated_by?: string
            }
          }
        | {
            __component?: 'block.hero' | 'block.section'
            id: string
            title: string
            buttonLabel: string
            buttonUrl: string
            description: string
            image?: {
              id: string
              name: string
              alternativeText?: string
              caption?: string
              width?: number
              height?: number
              formats?: object
              hash: string
              ext?: string
              mime: string
              size: number
              url: string
              previewUrl?: string
              provider: string
              provider_metadata?: object
              related?: string
              created_by?: string
              updated_by?: string
            }
            imagePosition: 'left' | 'right'
          }
      )[]
    
      /** @format date-time */
      published_at?: string
    }
    

    Il y a deux petits soucis, le premier concerne le champs slug typé en uid dont la spécification swagger ne définie pas le type et qui est donc inconnu. Pour corriger cela, ajoutez un fichier nommé types.d.ts afin de définir globalement ce type (une string ici) :

    type uid = string
    

    Le deuxième problème concerne le typage de la propriété __component de nos blocks, cela devrait être :

    export interface Page {
      // ...
      blocks?: (
        | {
    -        __component?: "block.hero" | "block.section";
    +        __component?: "block.hero";
            //...
            };
          }
        | {
    -      __component?: "block.hero" | "block.section";
    +      __component?:  "block.section";
          //...
          }
      )[];
    }
    

    Nous avons corrigé ce problème par l'intermédiaire d'une PR sur Strapi mais celle-ci est toujours en attende de review : https://github.com/strapi/strapi/pull/10595. En attendant que celle-ci soit mergée dans Strapi vous pouvez éditer votre type ou bien appliquer un patch sur votre dépendance Strapi.

    Nous pouvons désormais implémenter le routing dans Next.js.

    Implémentation du routing CMS (génération statique)

    Nous allons créer un fichier [pageName].tsx dans le dossier pages de Next.js (plus d'infos sur le routing de Next.js dans notre article dédié).

    L'utilisation de la syntaxe [pageName] nous permet de créer une route "wildcard" pour afficher nos pages créées dynamiquement depuis Strapi. Nous allons demander à Next.js de créer nos pages de manière statique (au build puis de manière incrémentale) grâce à l'utilisation des deux méthodes getStaticPaths et getStaticProps :

    // pages/[pageName].tsx
    
    import { GetStaticProps, NextPage } from "next"
    import { Api, Page } from "../typings/api"
    import BlockRenderer from "../components/BlockRenderer"
    
    const client = new Api()
    
    const CmsPage: NextPage<{ page: Page }> = ({ page }) => {
      return <BlockRenderer blocks={page.blocks}  />
    }
    
    export async function getStaticPaths() {
      const { data: pages } = await client.pages.pagesList()
    
      const paths = pages.map((page) => ({
        params: {
          pageName: page.slug,
        },
      }))
    
      return {
        paths,
        fallback: 'blocking',
      }
    }
    
    export const getStaticProps: GetStaticProps = async ({ params }) => {
      const pageName = params!.pageName! as string
    
      try {
        const { data: page } = await client.pages.pagesDetail(pageName)
    
        return {
          props: {
            page,
          },
          revalidate: 60,
        }
      } catch (e) {
        return {
          notFound: true,
        }
      }
    }
    
    export default CmsPage
    

    Par défaut l'API de Strapi utilise l'id dans l'url, vous pouvez utiliser le slug en suivant ce guide.

    Nous retournons dans la méthode getStaticPaths tous les chemins de nos pages CMS afin que Next.js puisse les compiler lors du build. Enfin nous récupérons les informations de la page (titre, blocks…) dans la méthode getStaticProps. Nous renseignons l'attribut revalidate à 60 afin de rafraîchir une page si celle-ci n'a pas été affichée depuis au moins 60 secondes.

    Il ne nous reste plus qu'à implémenter le composant <BlockRenderer /> pour afficher les blocks :

    import React from 'react'
    import { Page } from '../typings/api'
    
    const BlockRenderer = ({ blocks }: { blocks: Page['blocks'] }) => {
      return (
        <>
          {blocks?.map((block) => {
            switch (block.__component) {
              case 'block.hero':
                // Add the markup for the Hero block
                return <div />
              case 'block.section':
                // Add the markup for the Section block
                return <div />
              default:
                null
            }
          })}
        </>
      )
    }
    
    export default BlockRenderer
    

    Grâce à nos types, TypeScript infère bien sur le nom du block et propose uniquement ses propriétés :

    Block Hero

    Block Section

    La combinaison de Strapi, TypeScript et Next.js offre donc un système élégant pour développer rapidement un système de CMS sans laisser de côté l'expérience développeur (typages TypeScript) et la performance grâce à la génération statique.

    À découvrir également

    Premier Octet vous accompagne dans le développement de vos projets avec strapi

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

    Suivez nos aventures

    GitHub

    X

    Flux RSS

    Bluesky

    Naviguez à vue