mise a jour structure SEO

This commit is contained in:
sebvtl728
2025-11-05 01:08:39 +01:00
parent 5909871dec
commit 1890d830e1
12 changed files with 3157 additions and 205 deletions
+149
View File
@@ -0,0 +1,149 @@
import { createServer } from "node:http";
import { readFile, stat, writeFile, mkdir } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import puppeteer from "puppeteer";
import { PUBLIC_ROUTES, NOT_FOUND_ROUTE } from "./routes.mjs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const distDir = path.resolve(__dirname, "../dist");
const routes = PUBLIC_ROUTES.filter((route) => route.prerender).map(
(route) => route.path
);
const mimeTypes = {
".html": "text/html",
".js": "application/javascript",
".css": "text/css",
".json": "application/json",
".png": "image/png",
".jpg": "image/jpeg",
".jpeg": "image/jpeg",
".avif": "image/avif",
".webp": "image/webp",
".svg": "image/svg+xml",
".ico": "image/x-icon",
".txt": "text/plain",
".xml": "application/xml",
".map": "application/json",
".gz": "application/gzip",
".br": "application/brotli",
};
function serveStaticFile(res, filePath) {
const ext = path.extname(filePath).toLowerCase();
const contentType = mimeTypes[ext] ?? "application/octet-stream";
res.statusCode = 200;
res.setHeader("Content-Type", contentType);
return readFile(filePath).then((data) => res.end(data));
}
function createStaticServer() {
const server = createServer(async (req, res) => {
try {
const requestUrl = new URL(req.url ?? "/", "http://localhost");
let pathname = decodeURIComponent(requestUrl.pathname);
const hasExtension = path.extname(pathname) !== "";
if (pathname.endsWith("/")) {
pathname = pathname.slice(0, -1);
}
let filePath = path.join(distDir, pathname);
try {
const fileStat = await stat(filePath);
if (fileStat.isDirectory()) {
filePath = path.join(filePath, "index.html");
}
await serveStaticFile(res, filePath);
return;
} catch (error) {
if (hasExtension) {
res.statusCode = 404;
res.end("Not found");
return;
}
filePath = path.join(distDir, "index.html");
await serveStaticFile(res, filePath);
}
} catch (error) {
res.statusCode = 500;
res.end(`Server error: ${error instanceof Error ? error.message : error}`);
}
});
return new Promise((resolve) => {
server.listen(0, () => {
const address = server.address();
if (address && typeof address === "object") {
resolve({ server, port: address.port });
} else {
resolve({ server, port: 4173 });
}
});
});
}
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function renderRoute(browser, baseUrl, route, outputFile) {
const page = await browser.newPage();
const targetUrl = `${baseUrl}${route}`;
try {
await page.goto(targetUrl, {
waitUntil: ["networkidle0", "domcontentloaded"],
timeout: 60000,
});
} catch (error) {
console.warn(
`[prerender] Navigation to ${targetUrl} failed: ${
error instanceof Error ? error.message : error
}`
);
}
try {
// Donne un léger délai pour laisser React terminer le rendu asynchrone
await delay(1500);
const html = await page.content();
const outputPath =
outputFile ??
(route === "/"
? path.join(distDir, "index.html")
: path.join(distDir, route.replace(/^\//, ""), "index.html"));
await mkdir(path.dirname(outputPath), { recursive: true });
await writeFile(outputPath, html, "utf8");
console.log(
`✔ prerendered ${route} -> ${path.relative(distDir, outputPath) || "index.html"}`
);
} finally {
await page.close();
}
}
async function main() {
const { server, port } = await createStaticServer();
const baseUrl = `http://127.0.0.1:${port}`;
const browser = await puppeteer.launch({ headless: true });
try {
for (const route of routes) {
await renderRoute(browser, baseUrl, route);
}
await renderRoute(browser, baseUrl, NOT_FOUND_ROUTE, path.join(distDir, "404.html"));
} finally {
await browser.close();
server.close();
}
}
main().catch((error) => {
console.error("[prerender] Failed to prerender routes:", error);
process.exit(1);
});
+70
View File
@@ -0,0 +1,70 @@
const withTrailingSlash = (path) => {
if (!path || path === "/") return "/";
return path.endsWith("/") ? path : `${path}/`;
};
export const PUBLIC_ROUTES = [
{ path: "/", href: "/", label: "Accueil", prerender: true },
{
path: "/services/prestation-maitrise-oeuvre",
href: withTrailingSlash("/services/prestation-maitrise-oeuvre"),
label: "Création de site web",
prerender: true,
},
{
path: "/services/formations-web",
href: withTrailingSlash("/services/formations-web"),
label: "Formations web",
prerender: true,
},
{
path: "/services/electricite",
href: withTrailingSlash("/services/electricite"),
label: "Graphisme",
prerender: true,
},
{
path: "/services/service-securite-incendie",
href: withTrailingSlash("/services/service-securite-incendie"),
label: "Podcast",
prerender: true,
},
{
path: "/services/expertises-tce",
href: withTrailingSlash("/services/expertises-tce"),
label: "Formations professionnelles",
prerender: true,
},
{
path: "/nosFormations",
href: withTrailingSlash("/nosFormations"),
label: "Nos formations",
prerender: true,
},
{
path: "/posts",
href: withTrailingSlash("/posts"),
label: "Actualités",
prerender: true,
},
{
path: "/contact",
href: withTrailingSlash("/contact"),
label: "Contact",
prerender: true,
},
{
path: "/search",
href: withTrailingSlash("/search"),
label: "Recherche",
prerender: true,
},
{
path: "/sitemap.xml",
href: "/sitemap.xml",
label: "Plan du site complet",
prerender: false,
},
];
export const NOT_FOUND_ROUTE = "/__404";
+50
View File
@@ -0,0 +1,50 @@
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { PUBLIC_ROUTES } from "./routes.mjs";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const indexPath = path.resolve(__dirname, "../index.html");
const LIST_INDENT = " ";
const WRAPPER_INDENT = " ";
const generateListItems = () =>
PUBLIC_ROUTES.map(
({ path, href, label }) => {
const targetHref = href ?? path;
return `${LIST_INDENT}<li><a href="${targetHref}">${label}</a></li>`;
}
).join("\n");
async function main() {
const originalContent = await readFile(indexPath, "utf8");
const navPattern = new RegExp(
`${WRAPPER_INDENT}<ul class="no-js-nav">[\\s\\S]*?${WRAPPER_INDENT}<\\/ul>`,
"m"
);
const match = originalContent.match(navPattern);
if (!match) {
throw new Error(
"Bloc <ul class=\"no-js-nav\"> introuvable dans index.html. Impossible de mettre à jour la navigation noscript."
);
}
const replacement = `${WRAPPER_INDENT}<ul class="no-js-nav">\n${generateListItems()}\n${WRAPPER_INDENT}</ul>`;
const updatedContent = originalContent.replace(navPattern, replacement);
if (updatedContent !== originalContent) {
await writeFile(indexPath, updatedContent, "utf8");
console.log("✔ Mise à jour de la navigation noscript dans index.html");
} else {
console.log(" Navigation noscript déjà à jour");
}
}
main().catch((error) => {
console.error("[update-noscript] Échec de la mise à jour :", error);
process.exit(1);
});