diff --git a/README.md b/README.md index 48c70f7..51084a1 100644 --- a/README.md +++ b/README.md @@ -25,11 +25,11 @@ The build pipeline performs the following steps: 1. Compiles TypeScript and builds the client production assets via Vite. 2. Runs the Open Graph image generator (`scripts/og.ts`) to pre-render cards for all routes and patches the output HTML files with page-specific titles, descriptions, and JSON-LD breadcrumb schemas. -3. Runs the sitemap generator (`scripts/sitemap.ts`) to scan the build output directory and automatically generate a fresh `sitemap.xml` for all static routes. +3. Runs the sitemap generator (`scripts/gen-sitemap.mjs`) to generate a fresh `sitemap.xml` covering all static and dynamic routes. ## SEO & Metadata -- **`robots.txt`**: Located in `public/robots.txt` and copied to the build root. It allows indexing on all paths and points crawlers to the sitemap location. +- **`robots.txt`**: Located in `public/robots.txt` and copied to the build root. It allows search engine crawling while excluding staging, preview, admin, and 404 routes, and points crawlers to the sitemap location. - **`sitemap.xml`**: Dynamically compiled during build time into `dist/sitemap.xml` and `public/sitemap.xml`. - **JSON-LD**: Embedded in the site's `
`: - Global `Organization` schema representing Wraith Protocol. diff --git a/package.json b/package.json index af17847..ae768a9 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dev": "vite", "build": "tsc -b && vite build && node scripts/inline-css.js && tsx scripts/og.ts && tsx scripts/sitemap.ts && node scripts/gen-rss.mjs", "og:generate": "tsx scripts/og.ts", + "generate:sitemap": "node scripts/gen-sitemap.mjs", "preview": "vite preview", "test": "vitest run", "test:e2e": "playwright test", diff --git a/public/robots.txt b/public/robots.txt index 6a918b6..d39166b 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -1,4 +1,13 @@ User-agent: * Allow: / +# Exclude staging, preview, administrative, and 404 routes +Disallow: /staging/ +Disallow: /staging +Disallow: /_staging/ +Disallow: /preview/ +Disallow: /admin/ +Disallow: /404 +Disallow: /api/ + Sitemap: https://usewraith.xyz/sitemap.xml diff --git a/scripts/gen-sitemap.mjs b/scripts/gen-sitemap.mjs new file mode 100644 index 0000000..bd778dc --- /dev/null +++ b/scripts/gen-sitemap.mjs @@ -0,0 +1,112 @@ +import { writeFileSync, readFileSync, readdirSync, statSync, existsSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const rootDir = join(__dirname, '..'); +const distDir = join(rootDir, 'dist'); +const publicDir = join(rootDir, 'public'); +const siteUrl = 'https://usewraith.xyz'; + +/** + * Known static application routes + */ +const knownRoutes = [ + '/', + '/faq', + '/privacy', + '/use-cases', + '/roadmap', + '/case-studies', + '/stellar', + '/careers', + '/press', +]; + +/** + * Dynamically extract routes from case studies data + */ +function getCaseStudyRoutes() { + const routes = []; + const csPath = join(rootDir, 'src', 'data', 'case-studies.json'); + if (existsSync(csPath)) { + try { + const data = JSON.parse(readFileSync(csPath, 'utf8')); + if (Array.isArray(data.entries)) { + for (const entry of data.entries) { + if (entry.slug) { + routes.push(`/case-studies/${entry.slug}`); + } + } + } + } catch (err) { + console.warn('Could not read case-studies.json:', err.message); + } + } + return routes; +} + +/** + * Discover html routes from dist build directory if available + */ +function getDistRoutes(dir, base = '') { + const routes = []; + if (!existsSync(dir)) return routes; + + const files = readdirSync(dir); + if (files.includes('index.html') && base) { + routes.push(base); + } + + for (const file of files) { + if (file === 'og' || file === '404' || file.startsWith('.')) continue; + const fullPath = join(dir, file); + if (statSync(fullPath).isDirectory()) { + routes.push(...getDistRoutes(fullPath, `${base}/${file}`)); + } + } + + return routes; +} + +function generateSitemap() { + try { + const csRoutes = getCaseStudyRoutes(); + const distRoutes = existsSync(distDir) ? getDistRoutes(distDir) : []; + + const allRoutes = Array.from( + new Set([...knownRoutes, ...csRoutes, ...distRoutes]) + ).filter((r) => r && r !== '/404' && !r.includes('/staging') && !r.includes('/preview')); + + const today = new Date().toISOString().split('T')[0]; + + const sitemapXml = ` +