Implémenter dynamiquement l’OGP (Open Graph Protocol) sur Next

Javascript - NextJS

Permettez-moi de commencer par une question :

sur quel lien êtes-vous le plus susceptible de cliquer, celui du premier aperçu et du second ?

1- Premier aperçu

gfrysyIoFyODQAAAABJRU5ErkJggg==

2- Deuxième aperçu

VYppUOqA+V4AAAAASUVORK5CYII=

De toute évidence, l'ajout d'une image d'aperçu des médias sociaux est un excellent moyen d'améliorer l'aspect professionnel de votre site Web et sûrement aussi d’augmenter votre taux de clics. Ces images de médias sociaux sont spécifiées à l'aide du protocole Open Graph, qui est une spécification introduite par Facebook et qui est désormais prise en charge par tout logiciel prévisualisant les URL pour vous. Une chose qui est très importante et qui manquait à mon site jusqu’à maintenant. 

Heureusement, tout a une solution et on a la possibilité de manipuler ces aperçus de liens comme on le souhaite. Cela peut être connecté à un CMS afin que les éditeurs puissent choisir une image qui convient le mieux à leur page de destination, article de blog, article de presse, etc. 

Solution et possibilités  

Heureusement, tout a une solution et on a la possibilité de manipuler ces aperçus de liens comme on le souhaite. Cela peut être connecté à un CMS afin que les éditeurs puissent choisir une image qui convient le mieux à leur page de destination, article de blog, article de presse, etc.

On peut mettre également une image par défaut qui sera être utilisée tout le temps ou même générer une image à la demande.

Il est important que l'aperçu donne à l'utilisateur une idée claire de ce à quoi il peut s'attendre sur la page Web, comme une vignette sur une vidéo YouTube.

Mais que se passe-t-il si le contenu de la page n'est pas visuel du tout et qu'aucune photo ne correspond vraiment au contenu de la page ? Et si nous avions beaucoup de ces pages, avec du contenu généré dynamiquement ? Heureusement, nous ne sommes pas limités à la photographie, nous pouvons utiliser du texte ! Le meilleur exemple en est la façon dont GitHub génère des aperçus pour les URL des problèmes : ils génèrent une image contenant suffisamment d'informations pour que les utilisateurs sachent sur quoi ils vont cliquer. Ces images sont générées à la demande, car il serait impossible de stocker une image pour chaque pull request ou problème GitHub.


Dans cet article, nous allons découvrir comment créer ces images Open Graph dynamiques avec Next.js.

On va prendre mon site comme exemple et voyons ensemble ce que cela va donner.

Je vais commencer par lister mes besoins et le comportement que j’attends de mes pages, plus précisément les pages détail des articles, des tutoriels, etc. :

  • Chaque page détail d’un post doit obligatoirement avoir un aperçu.
  • Je dois avoir la possibilité de choisir l’image à utiliser comme aperçu lors de l’édition du post
  • Si aucune image n’est choisie lors de l’édition, une image par défaut sera utilisée à la place
  • On doit avoir le titre du post en dessous de l’image et si on n’est pas dans la page détail d’un post, l’intitulé de la page devra être utilisé à la place
  • On doit avoir la catégorie du post en dessous du titre
  • Si le post a des tags, on les mettra à côté de la catégorie.

Ceci est l’image qu’on utilisera par défaut : 

Hbd9GDV7AAAAAElFTkSuQmCC

En quelques lignes, voilà ce qu’on s’apprête à implémenter maintenant.

Pratiquement, voici les étapes à suivre :

1- Création du composant « Meta tags »

On va, avant tout chose, créer le composant qui se chargera de faire le rendu des différents tags d’Open Graph.

Principalement on a besoin de 3 balises :

  • Le titre : title
  • Les metas : meta
  • Et un élément vers une ressource extérieure : link

Pour que tout soit plus clair pour vous, je mets ici rapidement le contenu de mon composant: 

const MetaTags = ({ title, description, url, type, domain, image }) => {
    return (
        <Head>
            {/* <!-- HTML Meta Tags --> */}
            <title>{title}</title>
            <meta charSet="utf-8" />
            <meta name="description" content={description} />

            {/* <!-- Facebook Meta Tags --> */}
            <meta property="og:url" content={url} />
            <meta property="og:type" content={type || 'website'} />
            <meta property="og:title" content={title} />
            <meta property="og:description" content={description} />
            <meta property="og:image" content={image} />

            {/* <!-- Twitter Meta Tags --> */}
            <meta name="twitter:card" content="summary_large_image" />
            <meta property="twitter:domain" content={domain} />
            <meta property="twitter:url" content={url} />
            <meta name="twitter:title" content={title} />
            <meta name="twitter:description" content={description} />
            <meta name="twitter:image" content={image} />
            <link rel="icon" href="/favicon.ico" />
        </Head>
    )
}

Comme vous le voyez, j’ai implémenté les metas tag OG pour Facebook et Twitter, mais normalement cela devrait marcher pour la majorité des sites.

Passons maintenant à la création de l’api OG.

2- Implémentation de l’api OG

Pour que tout soit dynamiquement généré, on va commencer par créer un api qui implémentera toute la logique nécessaire à la génération de l’image.

L’api à pointer sera : /api/og

On enverra 4 paramètres à utiliser :

  • title: le titre de la page, l’article ou le tutoriel
  • category: la catégorie de l’article ou le tutoriel, si elle existe.
  • tags : le(s) tags de l’article ou du tutoriel, s’il y en a.
  • bannerUrl : l’url vers l’image à utiliser pour l’aperçu.

Pour vous épargner tout le blabla, voici l’ensemble des codes à obtenir à la fin : 

export default async function handler(req, res) {
    try {
        const { searchParams, protocol, host } = new URL(req.url)
        const defaultBanner = `${protocol}//${host}/photos/banners/nobanner.png`
        const title =
            searchParams?.get('title') ||
            'fredoandrianaivo.com | Site web personnel '
        const category = searchParams?.get('category')
        const tags = searchParams?.get('tags')?.split(',')
        const bannerUrl = searchParams?.get('bannerUrl')
        const bannerPreview = bannerUrl ? bannerUrl : defaultBanner

        return new ImageResponse(
            (
                <div
                    style={{
                        backgroundColor: 'black',
                        height: '100%',
                        width: '100%',
                        display: 'flex',
                        textAlign: 'center',
                        alignItems: 'center',
                        justifyContent: 'space-between',
                        flexDirection: 'column',
                        flexWrap: 'nowrap',
                        padding: '50px',
                    }}
                >

                    <div
                        style={{
                            width: '100%',
                            height: '440px',
                            display: 'flex',
                            justifyContent: 'center',
                            alignItems: 'center',
                            overflow: 'hidden',
                        }}
                    >

                        <img
                            src={bannerPreview}
                            width={'100%'}
                            height={'100%'}
                            alt={`Photo - ${title}`}
                            style={{
                                objectFit: 'cover',
                            }}
                        />
                    </div>

                    <div
                        style={{
                            display: 'flex',
                            backgroundClip: 'text',
                            color: 'transparent',
                            backgroundImage:
                                'linear-gradient(to right, #ec4899, #8b5cf6)',
                            fontWeight: 700,
                            fontSize: 35,
                        }}
                    >
                        {title}
                    </div>

                    <div
                        style={{
                            display: 'flex',
                            flexDirection: 'row',
                            gap: '10px',
                            alignItems: 'center',
                        }}
                    >
                        <div
                            style={{
                                display: 'flex',
                                color: 'white',
                                fontWeight: 400,
                                fontSize: 18,
                                marginLeft: 1,
                            }}
                        >
                            {category}
                            {tags.length ? ' -' : ''}
                        </div>

                        <div
                            style={{
                                display: 'flex',
                                flexDirection: 'row',
                            }}
                        >
                            {tags?.map((tag, i) => (
                                <div
                                    key={i}
                                    style={{
                                        color: 'black',
                                        fontSize: 15,
                                        padding: '5px',
                                        margin: '3px',
                                        backgroundColor: 'white',
                                        borderRadius: '8px',
                                    }}
                                >
                                    {tag}
                                </div>
                            ))}
                        </div>
                    </div>
                </div>
            ),
            {
                width: 1200,
                height: 630,
            }
        )
    } catch (e) {
        return new Response(`Failed to generate the image`, {
            status: 500,
        })
    }
}

3- Récupération de l’image aperçue

Maintenant qu’on a l’endpoint pour générer l’image à utiliser pour notre OG, revenons dans nos pages concernées pour implémenter les urls à envoyer avec les informations nécessaires comme les paramètres pour récupérer l’image dynamiquement.

Pour cela, rajoutez cette ligne dans vos pages :

const imageUrl =
        `${server}api/og?` +
        'title=' +
        encodeURIComponent(title) +
        '&category=' +
        encodeURIComponent(category) +
        '&tags=' +
        encodeURIComponent(tags?.map((el) => el.name).join(',')) +
        '&bannerUrl=' +
        encodeURIComponent(bannerUrl)

 Petite explication :

  • server: votre serveur (ex : https://fredoandrianaivo.com/).
  • encodeURIComponent(title) : le titre encodé avec la fonction encodeURIComponent .
  • encodeURIComponent(category) : la catégorie encodée avec la fonction encodeURIComponent.
  • encodeURIComponent(tags) : les tags du post si il en existe et séparés par virgule si ils sont plusieurs. Encodés avec la fonction encodeURIComponent.
  • encodeURIComponent(bannerUrl) : l’url de l’image à utiliser. Ici je fais référence à l’image que j’ai choisie lors de la création du post. On utilisera celle par défaut dans le cas contraire. Tourjous encodée avec la fonction encodeURIComponent.

4- Structuration et envoie des meta data

Maintenant qu’on a l’image qu’il nous fallait et les autres informations pour nos différents met tags d’OG, structurons les données et envoyons-les en tant que propriété au composant qui se charge du rendu final. 

const metaData = {
        image: imageUrl,
        title,
        description:
            body.replace(/<\/?[^>]+(>|$)/g, '').substring(0, 100) + '...',
        type: 'article',
        url: _resolvedUrl,
        domain,
    }

Explication de certaines variables :

body : pour envoyer une petite description à la longueur de 100 caractères et dans laquelle je remplace certains caractères.

resolvedUrl : l’url qui pointe vers l’actuel post

domain : la domaine url de mon site qui est https://fredoandrianaivo.com/).

Il nous suffit désormais de le passer au composant « MetaTags » qui se charge du rendu final.

<MetaTags {...metaData} />

Voilà, tout est bon et normalement vous devriez avoir un joli aperçu d’image lorsque vous le partagez vers les réseaux sociaux ou d’autres sites.

Ci-dessous, j’ai mis quelques captures de mon résultat sur Facebook, Twitter et Linkedin.

Sachez que vous n’êtes pas obligé de partager le lien pour voir le résultat, vous pouvez tout simplement utiliser ce site https://www.opengraph.xyz/

wH1XD8lWqPB9AAAAABJRU5ErkJggg==

D0t6XpJG203sAAAAAElFTkSuQmCC

BxUwx3MJCJj8AAAAAElFTkSuQmCC

Conclusion

L'ajout de balises Open Graph à votre site Web peut vous donner plus de contrôle sur la façon dont il apparaît dans les publications sur les réseaux sociaux et peut aider à améliorer le taux de clics. Vous pouvez également améliorer la façon dont votre site Web apparaît dans les SERP, ce qui peut finalement conduire à un meilleur classement du site Web.

L'ajout d'images Open Graph dynamiques dans Next.js demande très peu d'efforts, mais rend votre site 10 fois plus professionnel !

Quelques liens sur le sujet qui pourraient vous intéresser :

https://dev.to/inthepocket/dynamic-open-graph-images-with-nextjs-jg7

https://cloudinary.com/blog/guest_post/manage-open-graph-images-in-nextjs

 

Publié le 27/08/2023