Skip to content

Commit b2efce3

Browse files
author
Ibrahim BinAlshikh
committed
Add sitemap.xml generation and robots.txt
1 parent 1881a95 commit b2efce3

3 files changed

Lines changed: 124 additions & 0 deletions

File tree

website/public/robots.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
User-agent: *
2+
Allow: /
3+
4+
Sitemap: https://webfiori.com/sitemap.xml
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import type { Plugin } from 'vite'
2+
import fs from 'fs'
3+
import path from 'path'
4+
import { execSync } from 'child_process'
5+
6+
const BASE_URL = 'https://webfiori.com'
7+
8+
const STATIC_ROUTES = [
9+
{ path: '/', changefreq: 'weekly', priority: '1.0' },
10+
{ path: '/v3', changefreq: 'monthly', priority: '0.8' },
11+
{ path: '/features', changefreq: 'monthly', priority: '0.8' },
12+
{ path: '/getting-started', changefreq: 'monthly', priority: '0.8' },
13+
{ path: '/docs', changefreq: 'weekly', priority: '0.9' },
14+
{ path: '/blog', changefreq: 'weekly', priority: '0.9' },
15+
{ path: '/libraries', changefreq: 'monthly', priority: '0.6' },
16+
{ path: '/contributing', changefreq: 'monthly', priority: '0.5' },
17+
]
18+
19+
function getLastMod(filePath: string): string {
20+
try {
21+
const out = execSync(`git log -1 --format=%cI -- "${filePath}"`, {
22+
cwd: path.dirname(filePath),
23+
encoding: 'utf-8',
24+
}).trim()
25+
if (out) return out.slice(0, 10)
26+
} catch { /* ignore */ }
27+
try {
28+
return fs.statSync(filePath).mtime.toISOString().slice(0, 10)
29+
} catch {
30+
return new Date().toISOString().slice(0, 10)
31+
}
32+
}
33+
34+
function parseFrontmatterDate(raw: string): string {
35+
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---/)
36+
if (!match) return ''
37+
const dateMatch = match[1].match(/^date:\s*(.+)$/m)
38+
return dateMatch ? dateMatch[1].trim().replace(/["']/g, '') : ''
39+
}
40+
41+
function buildUrl(entry: { loc: string; lastmod?: string; changefreq?: string; priority?: string }): string {
42+
let xml = ` <url>\n <loc>${entry.loc}</loc>\n`
43+
if (entry.lastmod) xml += ` <lastmod>${entry.lastmod}</lastmod>\n`
44+
if (entry.changefreq) xml += ` <changefreq>${entry.changefreq}</changefreq>\n`
45+
if (entry.priority) xml += ` <priority>${entry.priority}</priority>\n`
46+
xml += ` </url>`
47+
return xml
48+
}
49+
50+
export default function sitemapPlugin(docsDir: string, blogDir: string): Plugin {
51+
return {
52+
name: 'vite-plugin-sitemap',
53+
apply: 'build',
54+
closeBundle() {
55+
const today = new Date().toISOString().slice(0, 10)
56+
const urls: string[] = []
57+
58+
// Static routes
59+
for (const route of STATIC_ROUTES) {
60+
urls.push(buildUrl({
61+
loc: `${BASE_URL}${route.path}`,
62+
lastmod: today,
63+
changefreq: route.changefreq,
64+
priority: route.priority,
65+
}))
66+
}
67+
68+
// Doc pages
69+
const docsResolved = path.resolve(docsDir)
70+
if (fs.existsSync(docsResolved)) {
71+
for (const file of fs.readdirSync(docsResolved)) {
72+
if (!file.endsWith('.md') || file === 'README.md' || file === 'index.md') continue
73+
const slug = file.replace('.md', '')
74+
const filePath = path.join(docsResolved, file)
75+
urls.push(buildUrl({
76+
loc: `${BASE_URL}/docs/${slug}`,
77+
lastmod: getLastMod(filePath),
78+
changefreq: 'monthly',
79+
priority: '0.7',
80+
}))
81+
}
82+
}
83+
84+
// Blog posts (only published)
85+
const blogResolved = path.resolve(blogDir)
86+
if (fs.existsSync(blogResolved)) {
87+
for (const file of fs.readdirSync(blogResolved)) {
88+
if (!file.endsWith('.md')) continue
89+
const filePath = path.join(blogResolved, file)
90+
const raw = fs.readFileSync(filePath, 'utf-8')
91+
const postDate = parseFrontmatterDate(raw)
92+
93+
// Exclude future posts
94+
if (postDate && postDate > today) continue
95+
96+
const slug = file.replace('.md', '')
97+
urls.push(buildUrl({
98+
loc: `${BASE_URL}/blog/${slug}`,
99+
lastmod: postDate || getLastMod(filePath),
100+
changefreq: 'yearly',
101+
priority: '0.7',
102+
}))
103+
}
104+
}
105+
106+
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
107+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
108+
${urls.join('\n')}
109+
</urlset>
110+
`
111+
112+
const outDir = path.resolve('dist')
113+
fs.mkdirSync(outDir, { recursive: true })
114+
fs.writeFileSync(path.join(outDir, 'sitemap.xml'), sitemap)
115+
console.log(`✓ sitemap.xml generated with ${urls.length} URLs`)
116+
},
117+
}
118+
}

website/vite.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import vue from '@vitejs/plugin-vue'
33
import vuetify from 'vite-plugin-vuetify'
44
import docsPlugin from './src/plugins/vite-plugin-docs'
55
import blogPlugin from './src/plugins/vite-plugin-blog'
6+
import sitemapPlugin from './src/plugins/vite-plugin-sitemap'
67

78
export default defineConfig({
89
plugins: [
910
vue(),
1011
vuetify(),
1112
docsPlugin('./docs-content'),
1213
blogPlugin('./blog-content'),
14+
sitemapPlugin('./docs-content', './blog-content'),
1315
],
1416
})

0 commit comments

Comments
 (0)