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>
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
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}`);
|
||||
}
|
||||
Reference in New Issue
Block a user