diff --git a/README.md b/README.md index 27f085d..795d2e5 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,11 @@ bestiary/ - **SEO Ready** - Structured data and semantic HTML - **Git-based CMS** - All content changes are version controlled +## 🗺 Sitemap + +The sitemap is available at: +[https://weirdanimals.life/sitemap.xml](https://weirdanimals.life/sitemap.xml) + ## 🔧 Development Tips ### Adding New Animal Categories diff --git a/package-lock.json b/package-lock.json index 9e482da..abb01b7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -88,6 +88,7 @@ "integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", @@ -1692,6 +1693,7 @@ "integrity": "sha512-EhBeSYX0Y6ye8pNebpKrwFJq7BoQ8J5SO6NlvNwwHjSj6adXJViPQrKlsyPw7hLBLvckEMO1yxeGdR82YBBlDg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -1733,6 +1735,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -1886,6 +1889,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001733", "electron-to-chromium": "^1.5.199", @@ -2238,6 +2242,7 @@ "integrity": "sha512-TS9bTNIryDzStCpJN93aC5VRSW3uTx9sClUn4B87pwiCaJh220otoI0X8mJKr+VcPtniMdN8GKjlwgWGUv5ZKA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -3490,6 +3495,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3531,6 +3537,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -3588,6 +3595,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz", "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -3597,6 +3605,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz", "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -4028,6 +4037,7 @@ "integrity": "sha512-J0SQBPlQiEXAF7tajiH+rUooJPo0l8KQgyg4/aMunNtrOa7bwuZJsJbDWzeljqQpgftxuq5yNJxQ91O9ts29UQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.6", diff --git a/src/plugins/sitemapPlugin.js b/src/plugins/sitemapPlugin.js new file mode 100644 index 0000000..91f1d49 --- /dev/null +++ b/src/plugins/sitemapPlugin.js @@ -0,0 +1,97 @@ +import path from "path"; +import fs from "fs"; + +const BASE_URL = 'https://weirdanimals.life/'; + +const getJsonFiles = (dir) => { + try { + return fs.readdirSync(dir) + .filter(file => file.endsWith('.json')) + .map(file => file.replace('.json', '')); + } catch (error) { + console.error(`Error reading directory ${dir}:`, error); + return []; + } +}; + +const getSitemapContent = () => { + const categories = getJsonFiles(path.resolve('src/data/categories')); + const animals = getJsonFiles(path.resolve('src/data/animals')); + const currentDate = new Date().toISOString().split('T')[0]; + + let xml = ` + + + ${BASE_URL}/ + ${currentDate} + daily + 1.0 + + + ${BASE_URL}/categories + ${currentDate} + weekly + 0.8 + `; + + categories.forEach(category => { + xml += ` + + ${BASE_URL}/category/${category} + ${currentDate} + weekly + 0.7 + `; + }); + + animals.forEach(animal => { + xml += ` + + ${BASE_URL}/animal/${animal} + ${currentDate} + monthly + 0.6 + `; + }); + + xml += ` +`; + + return xml; +}; + +const generateSitemap = (outDir) => { + const xml = getSitemapContent(); + + // Ensure outDir exists before writing + if (!fs.existsSync(outDir)) { + fs.mkdirSync(outDir, { recursive: true }); + } + + const sitemapPath = path.resolve(outDir, 'sitemap.xml'); + fs.writeFileSync(sitemapPath, xml); + console.log(`✓ Sitemap generated at ${sitemapPath}`); +}; + +export const sitemapPlugin = () => { + return { + name: 'generate-sitemap', + // Hook for production build + closeBundle() { + generateSitemap('dist'); + }, + // Hook for development server + configureServer(server) { + server.middlewares.use((req, res, next) => { + if (req.url === '/sitemap.xml') { + const xml = getSitemapContent(); + res.statusCode = 200; + res.setHeader('Content-Type', 'application/xml'); + res.end(xml); + } else { + next(); + } + }); + } + }; +}; diff --git a/vite.config.js b/vite.config.js index a493eb9..d5b5b1c 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,10 +1,11 @@ import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import path from "path"; +import { sitemapPlugin } from "./src/plugins/sitemapPlugin"; // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(), sitemapPlugin()], resolve: { alias: { "~features": path.resolve(__dirname, "./src/features"),