Files
ai-learning-platform/frontend/scripts/generate-sitemap.js
T
TradeMate Dev f4f17c10d8 build: 添加 postbuild sitemap 生成脚本 + robots.txt SEO
- package.json: 添加 postbuild 钩子自动生成 sitemap
- scripts/generate-sitemap.js: 站点地图生成器(78 条 URL)
- public/robots.txt: SEO 爬虫引导

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-06-23 21:24:20 +08:00

88 lines
3.1 KiB
JavaScript

const fs = require('fs');
const path = require('path');
const SITE_URL = 'https://yuzhiran.com';
const OUT_DIR = path.join(__dirname, '..', 'out');
const PRIORITIES = {
'/': 1.0,
'/tools': 0.9,
'/courses': 0.9,
'/skills': 0.8,
'/marketplace': 0.8,
'/practices': 0.8,
'/contents': 0.8,
'/prompts': 0.8,
'/models': 0.7,
'/sandbox': 0.7,
'/about': 0.5,
'/privacy': 0.3,
'/terms': 0.3,
};
function walkDir(dir, basePath = '') {
const entries = fs.readdirSync(dir, { withFileTypes: true });
const urls = [];
for (const entry of entries) {
const fullPath = path.join(dir, entry.name);
const relativeName = path.join(basePath, entry.name);
if (entry.isDirectory()) {
if (entry.name.startsWith('_') || entry.name.startsWith('.')) continue;
urls.push(...walkDir(fullPath, relativeName));
} else if (entry.name === 'page.html' || entry.name.endsWith('.html')) {
if (relativeName === 'index.html' || relativeName === 'page.html') {
urls.push({ path: '/', file: fullPath });
} else {
let urlPath = '/' + relativeName.replace(/\.html$/, '').replace(/\/index$/, '').replace(/\/page$/, '');
urls.push({ path: urlPath, file: fullPath });
}
}
}
return urls;
}
function generateSitemap(urls) {
const now = new Date().toISOString().split('T')[0];
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n';
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n';
const seen = new Set();
for (const url of urls) {
if (seen.has(url.path)) continue;
seen.add(url.path);
if (url.path.startsWith('/admin') || url.path.startsWith('/_next') || url.path.startsWith('/api') || url.path === '/404') continue;
if (url.path.includes('/[') || url.path.includes(']')) continue;
const depth = url.path.split('/').filter(Boolean).length;
const changefreq = depth <= 1 ? 'weekly' : 'monthly';
const priority = PRIORITIES[url.path];
const computedPriority = priority !== undefined ? priority : Math.max(0.1, 0.7 - depth * 0.1);
xml += ' <url>\n';
xml += ` <loc>${SITE_URL}${url.path}</loc>\n`;
xml += ` <lastmod>${now}</lastmod>\n`;
xml += ` <changefreq>${changefreq}</changefreq>\n`;
xml += ` <priority>${computedPriority.toFixed(1)}</priority>\n`;
xml += ' </url>\n';
}
xml += '</urlset>';
return xml;
}
if (!fs.existsSync(OUT_DIR)) {
console.error('out/ directory not found. Run next build first.');
process.exit(1);
}
const urls = walkDir(OUT_DIR);
console.log(`Found ${urls.length} HTML pages in output.`);
const sitemap = generateSitemap(urls);
const outputPath = path.join(OUT_DIR, 'sitemap.xml');
fs.writeFileSync(outputPath, sitemap, 'utf-8');
console.log(`Generated sitemap.xml with ${sitemap.split('<url>').length - 1} URLs → ${outputPath}`);
// Copy robots.txt from public/ to out/
const robotsSrc = path.join(__dirname, '..', 'public', 'robots.txt');
const robotsDst = path.join(OUT_DIR, 'robots.txt');
if (fs.existsSync(robotsSrc)) {
fs.copyFileSync(robotsSrc, robotsDst);
console.log(`Copied robots.txt → ${robotsDst}`);
}