diff --git a/package-lock.json b/package-lock.json index cef15e8..8398773 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "react-dom": "18.2.0", "react-modal-video": "^2.0.1", "sass": "^1.66.1", + "sitemap": "^8.0.0", "swiper": "^10.2.0", "wowjs": "^1.1.3" } @@ -483,12 +484,33 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/node": { + "version": "17.0.45", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", + "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", + "license": "MIT" + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/animate.css": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/animate.css/-/animate.css-4.1.1.tgz", "integrity": "sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==", "license": "MIT" }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, "node_modules/braces": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", @@ -1000,6 +1022,12 @@ "@parcel/watcher": "^2.4.1" } }, + "node_modules/sax": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz", + "integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==", + "license": "ISC" + }, "node_modules/scheduler": { "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", @@ -1009,6 +1037,25 @@ "loose-envify": "^1.1.0" } }, + "node_modules/sitemap": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-8.0.0.tgz", + "integrity": "sha512-+AbdxhM9kJsHtruUF39bwS/B0Fytw6Fr1o4ZAIAEqA6cke2xcoO2GleBw9Zw7nRzILVEgz7zBM5GiTJjie1G9A==", + "license": "MIT", + "dependencies": { + "@types/node": "^17.0.5", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.2.4" + }, + "bin": { + "sitemap": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", diff --git a/package.json b/package.json index d7b9ef1..7bfaec1 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "build": "next build", "start": "next start", "lint": "next lint", - "sass": "sass --watch public/assets/scss/main.scss:public/assets/css/main.css" + "sass": "sass --watch public/assets/scss/main.scss:public/assets/css/main.css", + "sitemap": "node scripts/generate-sitemap.js" }, "dependencies": { "isotope-layout": "^3.0.6", @@ -17,6 +18,7 @@ "react-dom": "18.2.0", "react-modal-video": "^2.0.1", "sass": "^1.66.1", + "sitemap": "^8.0.0", "swiper": "^10.2.0", "wowjs": "^1.1.3" } diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..dda681e --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1 @@ +https://sixty5street.com/daily1.0https://sixty5street.com/#aboutweekly0.8https://sixty5street.com/#popular-dishesmonthly0.7https://sixty5street.com/#sixty5-street-specialsweekly0.8https://sixty5street.com/#menuweekly0.7 \ No newline at end of file diff --git a/scripts/generate-sitemap.js b/scripts/generate-sitemap.js new file mode 100644 index 0000000..57a3c1c --- /dev/null +++ b/scripts/generate-sitemap.js @@ -0,0 +1,76 @@ +// scripts/generate-sitemap.js +const fs = require("fs"); +const path = require("path"); +const { SitemapStream, streamToPromise } = require("sitemap"); + +const hostname = "https://sixty5street.com"; +const addTrailingSlash = true; + +// Utility to determine if a trailing slash should be added +const shouldAddSlash = (url) => { + if (url === "/") return false; + if (/\.[a-z0-9]{2,6}(\?.*)?$/i.test(url)) return false; // skip files + return true; +}; + +// Format URL to ensure proper slashes and handle anchors +const formatUrl = (url) => { + // Split path and hash + let [pathPart, hashPart] = url.split("#"); + + // Remove extra leading slashes and ensure single starting slash + pathPart = "/" + pathPart.replace(/^\/+/, ""); + + // Add or remove trailing slash for pathPart + if (addTrailingSlash && shouldAddSlash(pathPart) && !pathPart.endsWith("/")) { + pathPart += "/"; + } + if (!addTrailingSlash && pathPart.endsWith("/") && pathPart !== "/") { + pathPart = pathPart.slice(0, -1); + } + + // Recombine with hash if it exists + return hashPart ? pathPart + "#" + hashPart : pathPart; +}; + +// List of URLs to include in sitemap +const sectionLinks = [ + { url: "/", changefreq: "daily", priority: 1.0 }, + { url: "/#about", changefreq: "weekly", priority: 0.8 }, + { url: "/#popular-dishes", changefreq: "monthly", priority: 0.7 }, + { url: "/#sixty5-street-specials", changefreq: "weekly", priority: 0.8 }, + { url: "/#menu", changefreq: "weekly", priority: 0.7 }, +]; + +// Format all URLs +const allLinks = sectionLinks.map((link) => ({ + ...link, + url: formatUrl(link.url), +})); + +// Generate sitemap.xml +async function generateSitemap() { + try { + const sitemap = new SitemapStream({ hostname }); + const writeStream = fs.createWriteStream( + path.resolve(__dirname, "../public/sitemap.xml") + ); + + sitemap.pipe(writeStream); + + console.log("📦 Writing URLs to sitemap:"); + allLinks.forEach((link) => { + console.log(" -", hostname + link.url); + sitemap.write(link); + }); + + sitemap.end(); + await streamToPromise(sitemap); + + console.log("✅ sitemap.xml created successfully!"); + } catch (error) { + console.error("❌ Error creating sitemap.xml:", error); + } +} + +generateSitemap(); \ No newline at end of file