Le SEO côté code
ce qui se joue avant le contenu
Avant les mots-clés, il y a la structure : balises, données structurées, performance, crawlabilité. Six leviers techniques — tous appliqués sur ce Lab, qui sert d'exemple vivant.
Données structurées — JSON-LD
Décrire le sens d'une page dans un langage que Google (et les IA) comprennent : c'est le rôle de Schema.org en JSON-LD. Résultat : rich results, meilleure compréhension, citations dans les moteurs génératifs (GEO).
TechArticle + un
BreadcrumbList. Teste n'importe quelle URL du Lab
dans le
Rich Results Test.
<!-- à placer dans le <head> de la page -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "TechArticle",
"headline": "SEO Technique — 6 démos",
"datePublished": "2026-10-06",
"author": { "@type": "Organization", "name": "OWL Concept" }
}
</script>
Balises meta — aperçu SERP
title et meta description forment ton
extrait dans les résultats. Vise ~60 caractères pour le titre,
~155 pour la description. Édite ci-dessous et vois l'aperçu en
direct.
<div class="serp">
<div class="serp__url">owlconcept.be › lab › seo</div>
<div class="serp__title" id="t">SEO Technique — OWL Lab</div>
<div class="serp__desc" id="d">Le SEO côté code : données structurées, meta…</div>
</div>
<label>Title <span id="tc"></span>
<input id="ti" maxlength="90" value="SEO Technique — OWL Lab">
</label>
<label>Description <span id="dc"></span>
<textarea id="di" rows="2" maxlength="200">Le SEO côté code : données structurées, meta…</textarea>
</label>
.serp { max-width: 600px; padding: 1rem; background: #fff; border-radius: 8px; }
.serp__url { color: #202124; font-size: .8rem; }
.serp__title { color: #1a0dab; font-size: 1.1rem; }
.serp__desc { color: #4d5156; font-size: .85rem; }
label { display: block; margin-top: .5rem; color: #c9d4e8; }
.over { color: #e84545; }
const ti = document.getElementById('ti');
const di = document.getElementById('di');
const T_MAX = 60, D_MAX = 155; // longueurs conseillées
function sync() {
document.getElementById('t').textContent = ti.value || 'Titre de la page';
document.getElementById('d').textContent = di.value || 'Description de la page.';
const tc = document.getElementById('tc'), dc = document.getElementById('dc');
tc.textContent = `${ti.value.length}/${T_MAX}`;
tc.className = ti.value.length > T_MAX ? 'over' : '';
dc.textContent = `${di.value.length}/${D_MAX}`;
dc.className = di.value.length > D_MAX ? 'over' : '';
}
ti.addEventListener('input', sync);
di.addEventListener('input', sync);
sync();
Core Web Vitals — le CLS en direct
Google note l'expérience via LCP (chargement), INP (réactivité) et CLS (stabilité visuelle). Le CLS punit les sauts de mise en page. Compare : sans puis avec réservation d'espace.
Texte déjà là.
Texte déjà là.
<div class="cls" id="box">
<div class="cls__media"></div> <!-- l'espace est réservé d'avance -->
<p>Texte déjà là.</p>
</div>
<button id="load">Simuler le chargement d'une image</button>
.cls {
padding: 1rem; color: #c9d4e8;
background: #131a24; border: 1px solid #1e2d44; border-radius: 8px;
}
/* réserver l'espace (hauteur connue d'avance) évite le saut → CLS = 0 */
.cls__media { aspect-ratio: 16 / 9; border-radius: 6px; background: #1e2d44; transition: background .3s; }
.cls.loaded .cls__media { background: #c9952a; }
document.getElementById('load').addEventListener('click', (e) => {
const on = document.getElementById('box').classList.toggle('loaded');
e.target.textContent = on ? 'Réinitialiser' : "Simuler le chargement d'une image";
});
sitemap.xml & robots.txt
Le sitemap.xml liste tes URL importantes ; le
robots.txt (à la racine du domaine) guide les
crawlers et référence le sitemap. Pense à autoriser les bots IA
pour être cité par les moteurs génératifs.
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://owlconcept.be/lab/</loc>
<lastmod>2026-07-07</lastmod>
<priority>1.0</priority>
</url>
</urlset>
# robots.txt — à la racine du domaine
User-agent: *
Allow: /
# autoriser les bots IA pour être cité par les moteurs génératifs (GEO)
User-agent: GPTBot
Allow: /
Sitemap: https://owlconcept.be/lab/sitemap.xml
Fil d'Ariane & maillage interne
Un fil d'Ariane balisé (BreadcrumbList) clarifie la
hiérarchie pour l'utilisateur et pour Google, qui
l'affiche dans ses résultats. Le maillage interne fait circuler
l'autorité entre tes pages.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "OWL Lab", "item": "https://owlconcept.be/lab/" },
{ "@type": "ListItem", "position": 2, "name": "SEO", "item": "https://owlconcept.be/lab/modules/seo/" },
{ "@type": "ListItem", "position": 3, "name": "SEO Technique" }
]
}
</script>
Rendu JS vs HTML — crawlabilité
Beaucoup de crawlers (surtout les bots IA) n'exécutent pas le
JavaScript. Un contenu injecté en JS peut leur être invisible.
La parade : contenu clé dans le HTML, ou fallback
<noscript>. Bascule pour voir la différence.
<main> <div id="grid"></div> <!-- vide : le crawler ne voit rien --> </main>
<main>
<div id="grid"></div> <!-- vide dans le HTML brut -->
<noscript>
<!-- fallback crawlable pour les bots qui n'exécutent pas le JS -->
<a href="/produits/a">Produit A</a>
<a href="/produits/b">Produit B</a>
</noscript>
</main>
// contenu injecté par JS → invisible pour un crawler qui n'exécute pas le JS
const grid = document.getElementById('grid');
['A', 'B', 'C', 'D'].forEach((n) => {
const a = document.createElement('a');
a.href = `/produits/${n.toLowerCase()}`;
a.textContent = `Produit ${n}`;
grid.appendChild(a);
});