mise a jour structure SEO
This commit is contained in:
@@ -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);
|
||||
});
|
||||
@@ -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";
|
||||
@@ -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);
|
||||
});
|
||||
Reference in New Issue
Block a user