Cet article a été mis à jour le 22/04/2024 à 19:44

Dans cet article, je vais partager avec vous quelques-unes de mes techniques pour améliorer le référencement de votre site Symfony 7. Je vais expliquer en détail comment je m'occupe des balises meta sur mes différentes pages, mon sitemap.xml, mon robots.txt, le multilinguisme de mon site et l'optimisation des images et des fichiers statiques avec Webpack Encore.

Je vais parler en tant que "je" pour souligner que c'est la méthode qui fonctionne pour moi, mais qu'elle peut ne pas être la solution idéale pour tout le monde 😉.

Gestion des balises meta

Pour une gestion optimale des balises meta, j'ai mis en place un système où je regroupe ces informations dans un block que j'ai nommé 'seo'. Ce block est intégré dans le layout utilisé pour toutes mes pages, ce qui me permet de modifier ces informations individuellement pour chaque template de page tout en conservant la structure de ces balises dans l'en-tête. Par exemple, si j'ai besoin d'ajuster ces informations pour une page produit basée sur un ID d'article, je peux le faire facilement. En plus de ce block, j'ai créé un block 'page_title_tag', pour gérer le titre de ma page de la même manière.

Pour que ce soit clair, voici ma structure de fichiers utilisée pour cet exemple :

templates/
    layout.html.twig
    home/
        home.html.twig
    blog/
        blog.html.twig
        article.html.twig

Et voici les blocks 'seo' et 'page_title_tag' dans l'entête de mon layout.html.twig :

...
<head>
    <title>Mon super site | {% block page_title_tag %}{% endblock %}</title>
    <meta charset=utf-8 >
    {% block seo %}{% endblock %}
...

Une fois les blocks prêt à être utilisés, je les utilise de cette manière dans mon template home.html.twig :

{% extends "layout.html.twig" %}

{% block page_title_tag %}Mon titre{% endblock %}
{% block seo %}
<meta name=description content="Ma description">
<!--Seo Réseaux-->
<meta property="og:title" content="Mon titre">
<meta property="og:type" content=website >
<meta property="og:description" content="Ma description">
<meta property="og:image" content="http://masuperimage.png">
<meta property="og:site_name" content="Mon super Site">
<meta property="og:url" content="{{ app.request.getSchemeAndHttpHost() }}">
<!--Seo Twitter-->
<meta property="twitter:title" content="Mon titre">
<meta property="twitter:type" content=website >
<meta property="twitter:description" content="Ma description">
<meta property="twitter:image" content="http://masuperimage.png">
<meta name="twitter:card" content=summary >

{% endblock %}
...

On remarque ainsi que les balises meta, cruciales pour l'optimisation du SEO, sont aisément gérables depuis le modèle de la page d'accueil, tout en étant intégrées au bon emplacement dans le layout global. Cette approche est valable non seulement pour la page d'accueil, mais également pour toutes les autres pages qui héritent du layout.html.twig. Cette flexibilité me permet d'ajuster facilement le titre, la description et les autres metas de chaque page au besoin.

Génération du sitemap.xml

Pour générer le sitemap.xml de mon site, j'ai créé un contrôleur qui se charge de récupérer les différentes URLs de mon site et de les afficher dans un fichier XML. J'ai organisé mon arborescence de fichiers de manière à inclure un template twig spécifique pour le sitemap.xml.

Maintenant mon arborescence de templates ressemble à ceci :

templates/
    layout.html.twig
    home/
        home.html.twig
    blog/
        blog.html.twig
        article.html.twig
    sitemap/
        sitemap.xml.twig

Pour que mon sitemap reprenne l'ensemble des URLs de mon site, je crée un tableau qui va contenir chacune d'entre elles en les ajoutant au moyen de la fonction generateUrl pour chacune des différentes routes. Une fois le tableau rempli avec l'ensemble des URLs, je l'envoie à mon template sitemap.xml.twig qui se chargera de les afficher. Voici le code de ma route :

#[Route("sitemap.xml", name: 'sitemap', format: 'xml')]
public function sitemapAction(Request $request, EntityManagerInterface $entityManager): Response
{
    $articles = $entityManager->getRepository(Article::class)->findAll();
    $urls = [];

    $urls[] = ['loc' => $this->generateUrl('home')];
    $urls[] = ['loc' => $this->generateUrl('blog')];
    foreach ($articles as $article){
        $urls[] = ['loc' => $this->generateUrl('article_view',["slug" => $room->getSlug($lang)])];
    }

    return $this->render('sitemap/sitemap.xml.twig', [
        'urls' => $urls,
    ]);

}

Dans ce contexte, mon site se limite à une page d'accueil et à des articles de blog. J'incorpore l'URL de la page d'accueil ainsi que celle de la page de blog dans mon tableau d'URLs. Ensuite, pour chaque article, j'inclus son URL dans ce même tableau.

Une fois le contrôleur finalisé, je crée mon template sitemap.xml.twig qui se présente comme suit :

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">
    {# On boucle sur les URLs #}
    {% for url in urls %}
        <url>
            <loc>
                {{ url.loc }}
            </loc>
            {% if url.lastmod is defined %}
                <lastmod>{{url.lastmod}}</lastmod>
            {% endif %}
            {% if url.changefreq is defined %}
                <changefreq>{{url.changefreq}}</changefreq>
            {% endif %}
            {% if url.priority is defined %}
                <priority>{{url.priority}}</priority>
            {% endif %}
        </url>
    {% endfor %}
</urlset>

On remarque que chaque URL est parcourue pour être affichée, et j'ai la capacité d'ajouter des balises supplémentaires telles que lastmod, changefreq et priority pour chaque URL. Dans le cas des articles de blog, par exemple, il est judicieux d'inclure lastmod, qui représente la date de la dernière modification de l'article.

L'intégration de ces informations nécessitera quelques ajustements au niveau du contrôleur :

#[Route("sitemap.xml", name: 'sitemap', format: 'xml')]
public function sitemapAction(Request $request, EntityManagerInterface $entityManager): Response
{
    $articles = $entityManager->getRepository(Article::class)->findAll();
    $urls = [];

    $urls[] = ['loc' => $this->generateUrl('home')];
    $urls[] = ['loc' => $this->generateUrl('blog')];
    foreach ($articles as $article){
        $urls[] = [
            'loc' => $this->generateUrl('article_view',["slug" => $room->getSlug($lang)]),
            'lastmod' => $article->getUpdatedAt() // Date de dernière modification de l'article
        ];
    }

    return $this->render('sitemap/sitemap.xml.twig', [
        'urls' => $urls,
    ]);

}

Et voilà, j'ai un sitemap.xml qui est généré dynamiquement en fonction des URLs de mon site et prêt à être utilisé pour optimiser le SEO de mon site.

Gestion du robots.txt

Pour gérer le fichier robots.txt, j'ai également créé une route dans mon contrôleur qui envoie les informations nécessaires à un template robots.txt.twig. Mon fichier robots.txt est très simple et indique aux robots d'exploration l'emplacement du sitemap.xml.

Mon arborescence ressemble maintenant à ça :

templates/
    layout.html.twig
    home/
        home.html.twig
    blog/
        blog.html.twig
        article.html.twig
    sitemap/
        sitemap.xml.twig
    robots/
        robots.txt.twig

Et voici ma route dans laquelle je génère l'URL de mon sitemap pour l'envoyer à mon template robots.txt.twig :

#[Route("robots.txt", name: 'robots', format: 'txt')]
public function robotsAction(Request $request): Response
{
    $sitemap = $this->generateUrl("sitemap", [], UrlGeneratorInterface::ABSOLUTE_URL);
    return $this->render('robots/robots.txt.twig', [
        'sitemap' => $sitemap,
    ]);

}

Ensuite, je crée mon template robots.txt.twig qui ressemble à ceci :

User-agent: *
Allow: /

Sitemap: {{ sitemap}}

Multilinguisme du site

La gestion du multilinguisme est cruciale pour le SEO. Il est important que chaque page de votre site possède un chemin traduit avec le préfixe de la langue.

Pour ce faire, j'ai ajouté un préfixe '_locale' à mes routes. La prise en compte des langues dépend de la configuration du champ 'enabled_locales" du fichier config/packages/translation.yaml de Symfony. Pour obtenir plus de détails sur cette configuration, je vous invite à consulter la documentation de Symfony. Ce préfixe doit être ajouté dans les annotations du contrôleur pour chaque route :

#[Route(path: '/{_locale}', name: 'home')]
public function home(Request $request): Response
{
    //Le code de mon contrôleur
}

Après avoir ajouté le préfixe pour ma page d'accueil, ce qui permettra d'accéder à mon site avec son nom de domaine suivi du chemin de la langue (par exemple : https://monsupersite.be/fr/, https://monsupersite.be/en/), je cherche maintenant à traduire les chemins des autres pages de mon site. Dans mon cas, je veux traduire le chemin de ma page de visualisation d'article. Pour assurer la traduction de chaque route dans chaque langue, il est possible de définir un tableau de traduction des routes directement dans les annotations pour chaque langue utilisée sur votre site.

#[Route(path: [
    'fr' => "/{_locale}/article/{slug}",
    'en' => '/{_locale}/news/{slug}', name: 'article_view')]
public function showView(Request $request, ManagerRegistry $doctrine): Response
{
    $article =  $doctrine->getRepository(Article::class)
        ->findOneBy(array('slug_'.$request->get("_locale") => $request->get("slug")));

    if(is_null($article)){
        throw $this->createNotFoundException('Page inexistante');
    }

    $params = [
        "article" => $article,
    ];
    return $this->render("blog/article.html.twig",$params);
}

Dans cette situation, l'URL est entièrement traduite en fonction de la langue du site. Pour chaque article, j'ai un champ de texte "slug" dans l'objet de la base de donnée qui est également traduit en fonction de la langue.

L'optimisation des images

Pour améliorer les images de mon site, j'utilise la librairie Imagine pour traiter les images téléchargées par les utilisateurs, puis les afficher ailleurs sur le site. Cette approche me permet de redimensionner les images selon les besoins, en créant des versions petites, moyennes, etc. En outre, je génère une version WebP de chaque image pour réduire leur poids, car le format WebP est plus léger que les formats JPG ou PNG. Pour en savoir plus sur le WebP, vous pouvez consulter ce lien.

Voici un exemple de service que j'utilise pour gérer les images de mon site :

class ImageOptimizer
{
    private const MAX_WIDTH = 900;
    private const MAX_HEIGHT = 600;

    private $imagine;

    public function __construct()
    {
        $this->imagine = new Imagine();
    }

    public function resize(string $filename): void
    {
        list($iwidth, $iheight) = getimagesize($filename);
        $ratio = $iwidth / $iheight;
        $width = self::MAX_WIDTH;
        $height = self::MAX_HEIGHT;
        if ($width / $height > $ratio) {
            $width = $height * $ratio;
        } else {
            $height = $width / $ratio;
        }

        $photo = $this->imagine->open($filename);
        $photo->resize(new Box($width, $height))->save($filename);
    }

    public function creatWebp(string $filename, string $mimetype, string $dir): void
    {
        if($mimetype == "image/jpeg"){
            $name = explode(".",$filename)[0];
            $nametosave = $name.".webp";
            fopen($nametosave, 'x');
            $img = imagecreatefromjpeg($filename);
            imagepalettetotruecolor($img);
            imagealphablending($img, true);
            imagesavealpha($img, true);
            imagewebp($img, $nametosave, 95);
            imagedestroy($img);
        }
        // Ici, je choisis de gérer uniquement les images au format jpeg, libre à vous d'ajouter les formats que vous voulez gérer
    }
    public function optimize(string $filename, string $mimetype, string $dir){
        // Ici, je redimensionne l'image et je crée une image webp
        $this->resize($filename);
        $this->creatWebp($filename, $mimetype, $dir);
    }
}

J'utilise également un autre service appelé FileUploader pour gérer le téléversement des images et les optimiser par la suite :

class FileUploader
{
    private $imageOptimizer;
    public function __construct(ImageOptimizer $imageOptimizer)
    {
        $this->imageOptimizer = $imageOptimizer;
    }
    public function upload(UploadedFile $file, string $targetDirectory): ?string
    {
        $fileName = $file->getClientOriginalName();
        try {
            $name = $file->move($targetDirectory, $fileName);
        } catch (FileException $e) {
            return $e->getMessage();
        }
        $this->imageOptimizer->optimize($name,$file->getClientMimeType(),$targetDirectory);
        return $fileName;
    }
}

Et au niveau de mon contrôleur, ce service est utilisé de cette manière :

#[Route('/admin/api/add_files', name: 'admin_files')]
public function registerFile(Request $request, FileUploader $fileUploader) : Response
{
    $images = $request->files->all();
    foreach ($images as $image){
        $fileUploader->upload($image,"/public/uploads");
    }
    return new Response("success");
}

Voilà ! Vous disposez désormais d'un système permettant de traiter les images téléchargées par les utilisateurs afin qu'elles soient automatiquement optimisées pour s'afficher aux bonnes dimensions et dans le bon format, ce qui contribuera à améliorer votre référencement SEO 👌.

L'optimisation des fichiers statics avec webpack Encore

Optimiser les feuilles de styles et les fichiers JavaScript en les minimisant est essentiel pour accélérer le chargement de votre site, ce qui est apprécié par les moteurs de recherche. Pour gagner en visibilité, il est donc judicieux de réduire la taille de ces fichiers. Pour ce faire, j'utilise le bundle webpack Encore, dont vous pouvez consulter la documentation. La configuration de webpack Encore se trouve dans le fichier webpack.config.js à la racine de votre projet. Je vous invite à parcourir la documentation pour découvrir toutes les fonctionnalités de cet outil, qui offre de nombreuses options d'optimisation. Vous pourrez également gérer les images statiques de votre site pour les redimensionner et les optimiser.

Conclusion

Dans cet article, j'ai partagé avec vous quelques-unes de mes techniques pour améliorer le référencement de votre site avec Symfony 7. J'ai expliqué comment je m'occupe des balises meta sur mes différentes pages, mon sitemap.xml, mon robots.txt, le multilinguisme de mon site, l'optimisation des images et des fichiers statiques avec Webpack Encore. J'espère que ces conseils vous aideront à optimiser le SEO de votre site Symfony et à améliorer sa visibilité sur les moteurs de recherche. Bonne optimisation SEO 😉 !

N'attendez plus pour lancer votre projet

Prenons contact