integration SEORank

This commit is contained in:
sebvtl728
2025-10-26 01:32:28 +02:00
parent 718eb8353b
commit e7e5c5e3fd
4 changed files with 810 additions and 379 deletions
File diff suppressed because it is too large Load Diff
+7 -4
View File
@@ -31,7 +31,7 @@ const Home = () => {
// 🔥 Ajout d'un timestamp pour éviter la mise en cache // 🔥 Ajout d'un timestamp pour éviter la mise en cache
const response = await api.get( const response = await api.get(
`wp/v2/pages/13?_fields=acf,rank_math_title,rank_math_description&_=${new Date().getTime()}` `wp/v2/pages/13?_fields=acf,link,rank_math_title,rank_math_description&_=${new Date().getTime()}`
); );
const pageContent = response.data; const pageContent = response.data;
setPageData(pageContent); setPageData(pageContent);
@@ -79,19 +79,22 @@ const Home = () => {
); );
} }
const { acf, rank_math_title, rank_math_description } = pageData || {}; const { acf, rank_math_title, rank_math_description, link } = pageData || {};
const fallbackUrl =
typeof window !== "undefined" ? window.location.href : "";
const pageUrl = link || fallbackUrl;
return ( return (
<div> <div>
{/* SEO */} {/* SEO */}
<SEO <SEO
postId={13} // ID WordPress valide pageUrl={pageUrl}
defaultTitle={rank_math_title || "centre de formation "} defaultTitle={rank_math_title || "centre de formation "}
defaultDescription={ defaultDescription={
rank_math_description || rank_math_description ||
"formation et reconversion professionnelle dans les métiers du numérique" "formation et reconversion professionnelle dans les métiers du numérique"
} }
defaultCanonicalUrl={acf?.canonical_url || window.location.href} defaultCanonicalUrl={acf?.canonical_url || pageUrl}
defaultOgImage={ defaultOgImage={
heroImage || heroImage ||
"https://preprod.octopusdesign.fr/api-octopus/server/wp-content/uploads/2025/01/Construction-logements-au-Mans-01.avif" "https://preprod.octopusdesign.fr/api-octopus/server/wp-content/uploads/2025/01/Construction-logements-au-Mans-01.avif"
+134 -52
View File
@@ -1,73 +1,155 @@
import React, { useEffect, useState } from "react"; import { useEffect, useMemo, useState } from "react";
import { Helmet } from "react-helmet-async"; import { Helmet } from "react-helmet-async";
import axios from "axios"; import api from "../api";
const SEO = ({ const DEFAULT_OG_IMAGE =
postId, "https://preprod.octopusdesign.fr/api-octopus/server/wp-content/uploads/2025/01/Construction-logements-au-Mans-01.avif";
defaultTitle = "Titre par défaut",
defaultDescription = "Description par défaut", const getWindowHref = () =>
defaultCanonicalUrl = "", typeof window !== "undefined" ? window.location.href : "";
defaultOgImage = "https://preprod.octopusdesign.fr/api-octopus/server/wp-content/uploads/2025/01/Construction-logements-au-Mans-01.avif"
const SEO = ({
pageUrl,
defaultTitle = "Titre par défaut",
defaultDescription = "Description par défaut",
defaultCanonicalUrl = "",
defaultOgImage = DEFAULT_OG_IMAGE,
}) => { }) => {
const [rankMathHead, setRankMathHead] = useState(null);
const [metaData, setMetaData] = useState({ const [shouldFallback, setShouldFallback] = useState(true);
title: defaultTitle,
description: defaultDescription, const resolvedCanonical = defaultCanonicalUrl || getWindowHref();
canonicalUrl: defaultCanonicalUrl, const targetUrl = pageUrl || resolvedCanonical;
ogTitle: defaultTitle,
ogDescription: defaultDescription,
ogImage: defaultOgImage,
});
useEffect(() => { useEffect(() => {
if (!postId) return; if (!targetUrl) return;
const fetchMetaData = async () => { const controller = new AbortController();
const fetchRankMathHead = async () => {
try { try {
const { data } = await axios.get( const { data } = await api.get("rankmath/v1/getHead", {
`https://preprod.octopusdesign.fr/api-octopus/server/wp-json/wp/v2/pages/${postId}` params: { url: targetUrl },
); signal: controller.signal,
});
if (data && data.rank_math_og) { if (
setMetaData((prev) => ({ data?.success &&
...prev, data.head &&
title: data.rank_math_og.og_title || prev.title, typeof window !== "undefined" &&
description: data.rank_math_og.og_description || prev.description, window.DOMParser
ogTitle: data.rank_math_og.og_title || prev.title, ) {
ogDescription: data.rank_math_og.og_description || prev.description, const parser = new window.DOMParser();
ogImage: data.rank_math_og.og_image || prev.ogImage, const parsedDocument = parser.parseFromString(
canonicalUrl: data.acf?.canonical_url || window.location.href `<!doctype html><html><head>${data.head}</head><body></body></html>`,
"text/html"
);
const headElement = parsedDocument.head;
const attributesToObject = (element) =>
Array.from(element.attributes).reduce((acc, attr) => {
acc[attr.name] = attr.value;
return acc;
}, {});
const metaTags = Array.from(headElement.querySelectorAll("meta")).map(
(meta) => attributesToObject(meta)
);
const linkTags = Array.from(headElement.querySelectorAll("link")).map(
(link) => attributesToObject(link)
);
const scriptTags = Array.from(
headElement.querySelectorAll("script")
).map((script) => ({
attributes: attributesToObject(script),
innerHTML: script.innerHTML,
})); }));
setRankMathHead({
title: headElement.querySelector("title")?.textContent || "",
meta: metaTags,
links: linkTags,
scripts: scriptTags,
});
setShouldFallback(false);
} else {
setRankMathHead(null);
setShouldFallback(true);
} }
} catch (error) { } catch (error) {
console.error("⚠️ Erreur lors de la récupération des métadonnées SEO :", error); if (!controller.signal.aborted) {
console.error(
"⚠️ Erreur lors de la récupération des métadonnées Rank Math :",
error
);
}
setRankMathHead(null);
setShouldFallback(true);
} }
}; };
fetchMetaData(); fetchRankMathHead();
}, [postId]);
return () => controller.abort();
}, [targetUrl]);
const rankMathNodes = useMemo(() => {
if (!rankMathHead) return null;
const renderMetaTags = rankMathHead.meta.map((attributes, index) => (
<meta key={`rank-math-meta-${index}`} {...attributes} />
));
const renderLinkTags = rankMathHead.links.map((attributes, index) => (
<link key={`rank-math-link-${index}`} {...attributes} />
));
const renderScriptTags = rankMathHead.scripts.map((script, index) => (
<script
key={`rank-math-script-${index}`}
{...script.attributes}
dangerouslySetInnerHTML={{ __html: script.innerHTML }}
/>
));
return (
<>
{rankMathHead.title && <title>{rankMathHead.title}</title>}
{renderMetaTags}
{renderLinkTags}
{renderScriptTags}
</>
);
}, [rankMathHead]);
return ( return (
<Helmet> <Helmet>
{/* SEO standard */} {!shouldFallback && rankMathNodes}
<title>{metaData.title}</title>
<meta name="description" content={metaData.description} /> {shouldFallback && (
{metaData.canonicalUrl && <link rel="canonical" href={metaData.canonicalUrl} />} <>
<title>{defaultTitle}</title>
{/* Open Graph (Facebook, LinkedIn) */} <meta name="description" content={defaultDescription} />
<meta property="og:type" content="website" /> {resolvedCanonical && (
<meta property="og:title" content={metaData.ogTitle} /> <link rel="canonical" href={resolvedCanonical} />
<meta property="og:description" content={metaData.ogDescription} /> )}
<meta property="og:image" content={metaData.ogImage} />
<meta property="og:url" content={metaData.canonicalUrl} /> <meta property="og:type" content="website" />
<meta property="og:title" content={defaultTitle} />
{/* Twitter Cards */} <meta property="og:description" content={defaultDescription} />
<meta name="twitter:card" content="summary_large_image" /> <meta property="og:image" content={defaultOgImage} />
<meta name="twitter:title" content={metaData.ogTitle} /> {resolvedCanonical && (
<meta name="twitter:description" content={metaData.ogDescription} /> <meta property="og:url" content={resolvedCanonical} />
<meta name="twitter:image" content={metaData.ogImage} /> )}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={defaultTitle} />
<meta name="twitter:description" content={defaultDescription} />
<meta name="twitter:image" content={defaultOgImage} />
</>
)}
</Helmet> </Helmet>
); );
}; };
export default SEO; export default SEO;
+50
View File
@@ -2,6 +2,7 @@ import axios from "axios";
import { getToken } from "./auth"; // 🔥 Import du token pour l'authentification import { getToken } from "./auth"; // 🔥 Import du token pour l'authentification
const API_URL = "https://preprod.octopusdesign.fr/api-octopus/server/wp-json/wp/v2"; const API_URL = "https://preprod.octopusdesign.fr/api-octopus/server/wp-json/wp/v2";
const RANKMATH_API_URL = "https://preprod.octopusdesign.fr/api-octopus/server/wp-json/rankmath/v1";
/** /**
* 🔹 Upload une image et retourne son ID * 🔹 Upload une image et retourne son ID
@@ -334,3 +335,52 @@ export async function fetchPages() {
return []; // ✅ Retourne un tableau vide en cas d'erreur pour éviter le plantage return []; // ✅ Retourne un tableau vide en cas d'erreur pour éviter le plantage
} }
} }
/**
* 🔹 Met à jour les métadonnées Rank Math (SEO) d'une page/post.
* @param {number} objectId - ID WordPress de la page.
* @param {object} meta - Objet des champs Rank Math à modifier.
*/
export async function updateRankMathMeta(objectId, meta = {}) {
const token = getToken();
if (!token) {
console.error("❌ Aucun token Rank Math trouvé !");
throw new Error("Utilisateur non authentifié.");
}
const sanitizedMeta = Object.entries(meta).reduce((acc, [key, value]) => {
const cleanedValue = typeof value === "string" ? value.trim() : value;
if (cleanedValue) {
acc[key] = cleanedValue;
}
return acc;
}, {});
if (!Object.keys(sanitizedMeta).length) {
console.warn("️ Aucun champ Rank Math fourni, mise à jour ignorée.");
return null;
}
try {
const response = await axios.post(
`${RANKMATH_API_URL}/updateMeta`,
{
objectType: "post", // ✅ Les pages WordPress sont du type "post".
objectID: objectId,
meta: sanitizedMeta,
},
{
headers: {
Authorization: `Basic ${token}`,
"Content-Type": "application/json",
},
}
);
console.log("✅ Métadonnées Rank Math mises à jour :", response.data);
return response.data;
} catch (error) {
console.error("❌ Erreur mise à jour Rank Math :", error.response?.data || error.message);
throw error;
}
}