En Suisse romande, un site sérieux existe souvent en deux langues. Français pour les clients d'ici, anglais pour les autres. Ce site ne fait pas exception : chaque page a sa jumelle, /fr/… et /en/….
Ce que la plupart des gens ne voient pas, c'est que la moitié du travail « bilingue » ne se fait pas dans le texte. Elle se fait dans le <head> de la page, une vingtaine de lignes que personne ne lit, mais que Google, LinkedIn et les lecteurs RSS lisent très attentivement.
Voici, balise par balise, ce que chaque page de stackshaka.com émet, et pourquoi. Rien de théorique : vous pouvez ouvrir le code source de cet article et tout vérifier.
Le canonical : une seule adresse officielle
<link rel="canonical" href="https://stackshaka.com/fr/blog/site-bilingue-balises-head/">
Une même page peut être atteinte de plusieurs façons : avec ou sans / final, en http, avec un paramètre de tracking collé par un réseau social. Pour un moteur de recherche, ce sont autant d'URL distinctes qui se partagent la visibilité.
Le canonical dit : « cette page-là, c'est l'originale ». Je mets toujours l'URL absolue, en https, avec le / final, exactement dans la forme où le serveur la sert. Si la redirection .htaccess impose le / final, le canonical doit le contenir aussi. Un canonical qui pointe vers une URL qui redirige, c'est un signal contradictoire.
Sur un site bilingue, le piège classique : mettre la version FR en canonical de la version EN « parce que c'est l'originale ». Non. Chaque langue est sa propre page originale. Le canonical de /en/… pointe vers /en/…. La relation entre les deux langues, c'est le rôle de la balise suivante.
Le hreflang : relier les deux langues sans les confondre
<link rel="alternate" hreflang="fr" href="https://stackshaka.com/fr/blog/site-bilingue-balises-head/">
<link rel="alternate" hreflang="en" href="https://stackshaka.com/en/blog/site-bilingue-balises-head/">
<link rel="alternate" hreflang="x-default" href="https://stackshaka.com/fr/blog/site-bilingue-balises-head/">
Sans hreflang, Google voit deux pages qui parlent de la même chose et peut afficher la version anglaise à quelqu'un qui cherche en français depuis Lausanne. Avec, il sait quelle version servir à qui.
Trois règles que je respecte systématiquement, parce que chacune a un mode d'échec bien connu :
- Réciprocité. La page FR déclare la page EN, et la page EN déclare la page FR. Si un seul côté le fait, Google ignore l'annotation. C'est la source d'erreur numéro un dans les rapports Search Console.
- Auto-référence. Chaque page se cite elle-même dans la liste. La page FR déclare aussi
hreflang="fr"vers sa propre URL. Ça paraît redondant ; c'est requis. - x-default. Il désigne la version à servir quand aucune langue ne correspond. Ici c'est le français, parce que c'est la langue principale du site et de sa clientèle. Un site orienté international mettrait plutôt l'anglais.
Dans le générateur de ce blog, les deux fichiers Markdown d'un article partagent un translationKey. C'est lui qui produit les trois lignes ci-dessus. Comme tout est généré, il n'y a aucune ligne à recopier à la main, donc aucune faute de frappe possible dans une URL. Dans la pratique, chaque article est publié dans les deux langues le même jour, ce qui évite le cas d'une page qui déclare une jumelle inexistante.
Open Graph : ce que les gens voient quand ils partagent
<meta property="og:type" content="article">
<meta property="og:title" content="… — Stackshaka">
<meta property="og:description" content="…">
<meta property="og:url" content="https://stackshaka.com/fr/blog/site-bilingue-balises-head/">
<meta property="og:image" content="https://stackshaka.com/og-image.jpg">
<meta name="twitter:card" content="summary_large_image">
Collez un lien sur LinkedIn, WhatsApp ou Slack : la vignette qui apparaît vient de ces lignes. Sans elles, vous obtenez une URL nue ou une image prise au hasard sur la page. Avec, vous contrôlez le titre, le résumé et le visuel.
Deux détails qui comptent. L'image doit être en URL absolue, pas /og-image.jpg, parce que le robot de LinkedIn ne résout pas les chemins relatifs comme un navigateur. Et og:url doit être identique au canonical, sinon les compteurs de partage et les aperçus se dispersent sur plusieurs adresses.
Le og:description reprend la meta description de la page. Une seule source de vérité : le champ description du frontmatter de l'article. Je n'écris pas trois résumés différents pour trois balises.
JSON-LD : dire aux machines qui a écrit quoi
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BlogPosting",
"headline": "…",
"datePublished": "2026-09-16",
"inLanguage": "fr",
"author": { "@type": "Person", "name": "Sylvain", "url": "https://stackshaka.com/fr/" },
"mainEntityOfPage": "https://stackshaka.com/fr/blog/site-bilingue-balises-head/"
}
</script>
Le JSON-LD ne change rien à l'affichage. Il décrit la page dans un vocabulaire que Google et, de plus en plus, les moteurs de réponse par IA comprennent sans ambiguïté : c'est un article de blog, publié à cette date, dans cette langue, par cette personne.
Le champ qui m'importe le plus est author. Sur un site d'indépendant, l'auteur est la marque. Un article signé par une personne identifiable, reliée à une page « à propos », pèse plus qu'un article anonyme. C'est exactement ce que les recommandations E-E-A-T de Google décrivent, et c'est gratuit.
inLanguage reprend la langue de la page. Là encore, la version EN dit "en", pas "fr". La cohérence entre lang sur <html>, le hreflang et inLanguage est ce qui évite qu'un robot classe une page dans la mauvaise langue.
Ce que je ne mets pas
Pas de meta keywords : ignorée par les moteurs depuis plus de quinze ans. Pas de balise generator qui annonce quel outil a produit la page : information inutile pour le lecteur, utile pour quelqu'un qui cherche des failles connues. Pas de script de statistiques dans le <head> : ce site n'en a aucun, la CSP le bloquerait de toute façon.
Et pas de balises générées « au cas où ». Chaque ligne du <head> a une raison d'être que je peux expliquer. Si je ne peux pas, elle sort.
Comment vérifier votre propre site
Trois gestes, cinq minutes :
- Ouvrez une page en FR, affichez le code source, cherchez
hreflang. Puis faites pareil sur la page EN. Les deux listes doivent être identiques. - Copiez l'URL dans le Post Inspector de LinkedIn ou dans un validateur Open Graph. Si le titre ou l'image sont faux, c'est là que ça se voit.
- Passez la page dans le test des résultats enrichis de Google. Le
BlogPostingdoit apparaître sans erreur.
Si l'une des trois étapes coince, ce n'est jamais grave, mais c'est presque toujours ce qui explique qu'une version linguistique « ne sort pas » dans les résultats.
En résumé
Un site bilingue bien fait, ce n'est pas seulement deux textes de qualité. C'est deux pages qui se déclarent mutuellement, chacune avec sa propre adresse officielle, un aperçu propre quand on la partage et une carte d'identité lisible par les machines. Sur ce site, tout ça sort d'un seul script et d'un frontmatter de huit champs. Il n'y a rien à retoucher à la main, et c'est la seule façon pour que ça reste juste dans le temps.
Si vous avez un site en deux langues et un doute sur ce que Google en comprend, écrivez-moi. Un audit de <head>, c'est rapide, et c'est souvent là que se cache le problème.