Infrastructure SEO complète : sitemap, meta descriptions, OG, Twitter Cards, JSON-LD, hreflang
- Ajout de site: 'https://jalil.arfaoui.net' et @astrojs/sitemap avec support i18n dans astro.config.mjs
- Création de src/components/SEO.astro : meta description, canonical, Open Graph, Twitter Cards, hreflang (fr/en/ar/x-default), JSON-LD Person (11 liens sameAs) sur chaque page et JSON-LD WebSite sur les pages d'accueil
- Création de src/utils/page-translations.ts : mapping centralisé des URLs entre langues
- Fix lang="en" hardcodé dans main.astro → lang dynamique + dir="rtl" pour l'arabe
- Ajout de meta descriptions ciblées sur les 13 pages principales (FR/EN/AR)
- Refactorisation du LanguageSwitcher pour utiliser le mapping centralisé
- Ajout de la directive Sitemap dans robots.txt
2026-02-21 14:32:56 +01:00
|
|
|
---
|
|
|
|
|
import type { ImageMetadata } from "astro";
|
|
|
|
|
import { getAlternateUrls } from "../utils/page-translations";
|
|
|
|
|
import defaultOgImage from "../assets/images/jalil-2.jpg";
|
|
|
|
|
|
|
|
|
|
interface Props {
|
|
|
|
|
title: string;
|
|
|
|
|
description: string;
|
|
|
|
|
ogImage?: ImageMetadata;
|
|
|
|
|
ogType?: string;
|
|
|
|
|
article?: { publishedTime?: string; tags?: string[] };
|
|
|
|
|
noindex?: boolean;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const {
|
|
|
|
|
title,
|
|
|
|
|
description,
|
|
|
|
|
ogImage,
|
|
|
|
|
ogType = "website",
|
|
|
|
|
article,
|
|
|
|
|
noindex = false,
|
|
|
|
|
} = Astro.props;
|
|
|
|
|
|
|
|
|
|
const siteUrl = Astro.site!.origin;
|
|
|
|
|
const canonicalUrl = new URL(Astro.url.pathname, siteUrl).href;
|
|
|
|
|
const imageUrl = new URL((ogImage ?? defaultOgImage).src, siteUrl).href;
|
|
|
|
|
|
|
|
|
|
// Locale detection
|
|
|
|
|
const pathname = Astro.url.pathname.replace(/\/$/, "") || "/";
|
|
|
|
|
const locale = pathname.startsWith("/en")
|
|
|
|
|
? "en"
|
|
|
|
|
: pathname.startsWith("/ar")
|
|
|
|
|
? "ar"
|
|
|
|
|
: "fr";
|
|
|
|
|
|
|
|
|
|
const ogLocaleMap: Record<string, string> = {
|
|
|
|
|
fr: "fr_FR",
|
|
|
|
|
en: "en_US",
|
|
|
|
|
ar: "ar_SA",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Hreflang
|
|
|
|
|
const alternateUrls = getAlternateUrls(pathname);
|
|
|
|
|
|
|
|
|
|
// Home pages for WebSite schema
|
|
|
|
|
const isHomePage = pathname === "/" || pathname === "/en" || pathname === "/ar";
|
|
|
|
|
|
|
|
|
|
// Person JSON-LD (on every page)
|
|
|
|
|
const personJsonLd = {
|
|
|
|
|
"@context": "https://schema.org",
|
|
|
|
|
"@type": "Person",
|
|
|
|
|
"@id": `${siteUrl}/#person`,
|
|
|
|
|
name: "Jalil Arfaoui",
|
|
|
|
|
url: siteUrl,
|
|
|
|
|
image: imageUrl,
|
|
|
|
|
jobTitle: "Software Craftsman",
|
|
|
|
|
knowsAbout: [
|
|
|
|
|
"Software Craftsmanship",
|
|
|
|
|
"TDD",
|
|
|
|
|
"DDD",
|
|
|
|
|
"Improv Theater",
|
|
|
|
|
"Photography",
|
|
|
|
|
],
|
|
|
|
|
sameAs: [
|
|
|
|
|
"https://www.linkedin.com/in/jalil/",
|
|
|
|
|
"https://github.com/JalilArfaoui",
|
|
|
|
|
"https://gitlab.gnome.org/Jalil",
|
|
|
|
|
"https://forge.tiqa.fr/jalil",
|
|
|
|
|
"https://framagit.org/jalil",
|
|
|
|
|
"https://www.malt.fr/profile/jalilarfaoui?overview",
|
|
|
|
|
"https://www.collective.work/profile/jalil-arfaoui-mrr",
|
|
|
|
|
"https://500px.com/p/jalilarfaoui",
|
|
|
|
|
"https://commons.wikimedia.org/wiki/User:JalilArfaoui",
|
|
|
|
|
"https://www.instagram.com/l.i.l.a.j",
|
|
|
|
|
"https://x.com/jalilarfaoui",
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
|
2026-02-21 14:58:40 +01:00
|
|
|
// Article JSON-LD (for blog posts / articles)
|
|
|
|
|
const articleJsonLd =
|
|
|
|
|
ogType === "article" && article
|
|
|
|
|
? {
|
|
|
|
|
"@context": "https://schema.org",
|
|
|
|
|
"@type": "BlogPosting",
|
|
|
|
|
headline: title,
|
|
|
|
|
description,
|
|
|
|
|
image: imageUrl,
|
|
|
|
|
url: canonicalUrl,
|
|
|
|
|
inLanguage: locale,
|
|
|
|
|
...(article.publishedTime && { datePublished: article.publishedTime }),
|
|
|
|
|
author: { "@id": `${siteUrl}/#person` },
|
|
|
|
|
}
|
|
|
|
|
: null;
|
|
|
|
|
|
Infrastructure SEO complète : sitemap, meta descriptions, OG, Twitter Cards, JSON-LD, hreflang
- Ajout de site: 'https://jalil.arfaoui.net' et @astrojs/sitemap avec support i18n dans astro.config.mjs
- Création de src/components/SEO.astro : meta description, canonical, Open Graph, Twitter Cards, hreflang (fr/en/ar/x-default), JSON-LD Person (11 liens sameAs) sur chaque page et JSON-LD WebSite sur les pages d'accueil
- Création de src/utils/page-translations.ts : mapping centralisé des URLs entre langues
- Fix lang="en" hardcodé dans main.astro → lang dynamique + dir="rtl" pour l'arabe
- Ajout de meta descriptions ciblées sur les 13 pages principales (FR/EN/AR)
- Refactorisation du LanguageSwitcher pour utiliser le mapping centralisé
- Ajout de la directive Sitemap dans robots.txt
2026-02-21 14:32:56 +01:00
|
|
|
// WebSite JSON-LD (only on home pages)
|
|
|
|
|
const websiteJsonLd = isHomePage
|
|
|
|
|
? {
|
|
|
|
|
"@context": "https://schema.org",
|
|
|
|
|
"@type": "WebSite",
|
|
|
|
|
name: "Jalil Arfaoui",
|
|
|
|
|
url: siteUrl,
|
|
|
|
|
inLanguage: locale,
|
|
|
|
|
author: { "@id": `${siteUrl}/#person` },
|
|
|
|
|
}
|
|
|
|
|
: null;
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
<!-- Meta tags -->
|
|
|
|
|
<meta name="description" content={description} />
|
|
|
|
|
<link rel="canonical" href={canonicalUrl} />
|
|
|
|
|
{noindex && <meta name="robots" content="noindex, nofollow" />}
|
|
|
|
|
|
|
|
|
|
<!-- Open Graph -->
|
|
|
|
|
<meta property="og:title" content={title} />
|
|
|
|
|
<meta property="og:description" content={description} />
|
|
|
|
|
<meta property="og:url" content={canonicalUrl} />
|
|
|
|
|
<meta property="og:image" content={imageUrl} />
|
|
|
|
|
<meta property="og:type" content={ogType} />
|
|
|
|
|
<meta property="og:locale" content={ogLocaleMap[locale]} />
|
|
|
|
|
<meta property="og:site_name" content="Jalil Arfaoui" />
|
|
|
|
|
{article?.publishedTime && (
|
|
|
|
|
<meta property="article:published_time" content={article.publishedTime} />
|
|
|
|
|
)}
|
|
|
|
|
{article?.tags?.map((tag) => (
|
|
|
|
|
<meta property="article:tag" content={tag} />
|
|
|
|
|
))}
|
|
|
|
|
|
|
|
|
|
<!-- Twitter Card -->
|
|
|
|
|
<meta name="twitter:card" content="summary_large_image" />
|
|
|
|
|
<meta name="twitter:site" content="@jalilarfaoui" />
|
|
|
|
|
<meta name="twitter:title" content={title} />
|
|
|
|
|
<meta name="twitter:description" content={description} />
|
|
|
|
|
<meta name="twitter:image" content={imageUrl} />
|
|
|
|
|
|
|
|
|
|
<!-- Hreflang -->
|
|
|
|
|
{alternateUrls && (
|
|
|
|
|
<>
|
|
|
|
|
<link rel="alternate" hreflang="fr" href={new URL(alternateUrls.fr, siteUrl).href} />
|
|
|
|
|
<link rel="alternate" hreflang="en" href={new URL(alternateUrls.en, siteUrl).href} />
|
|
|
|
|
<link rel="alternate" hreflang="ar" href={new URL(alternateUrls.ar, siteUrl).href} />
|
|
|
|
|
<link rel="alternate" hreflang="x-default" href={new URL(alternateUrls.fr, siteUrl).href} />
|
|
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
<!-- JSON-LD Person -->
|
|
|
|
|
<script type="application/ld+json" set:html={JSON.stringify(personJsonLd)} />
|
|
|
|
|
|
2026-02-21 14:58:40 +01:00
|
|
|
<!-- JSON-LD Article (blog posts only) -->
|
|
|
|
|
{articleJsonLd && (
|
|
|
|
|
<script type="application/ld+json" set:html={JSON.stringify(articleJsonLd)} />
|
|
|
|
|
)}
|
|
|
|
|
|
Infrastructure SEO complète : sitemap, meta descriptions, OG, Twitter Cards, JSON-LD, hreflang
- Ajout de site: 'https://jalil.arfaoui.net' et @astrojs/sitemap avec support i18n dans astro.config.mjs
- Création de src/components/SEO.astro : meta description, canonical, Open Graph, Twitter Cards, hreflang (fr/en/ar/x-default), JSON-LD Person (11 liens sameAs) sur chaque page et JSON-LD WebSite sur les pages d'accueil
- Création de src/utils/page-translations.ts : mapping centralisé des URLs entre langues
- Fix lang="en" hardcodé dans main.astro → lang dynamique + dir="rtl" pour l'arabe
- Ajout de meta descriptions ciblées sur les 13 pages principales (FR/EN/AR)
- Refactorisation du LanguageSwitcher pour utiliser le mapping centralisé
- Ajout de la directive Sitemap dans robots.txt
2026-02-21 14:32:56 +01:00
|
|
|
<!-- JSON-LD WebSite (home pages only) -->
|
|
|
|
|
{websiteJsonLd && (
|
|
|
|
|
<script type="application/ld+json" set:html={JSON.stringify(websiteJsonLd)} />
|
|
|
|
|
)}
|