Membuat peta situs XML dengan Claude Code
Buat peta situs Astro dan Node dengan hreflang, lastmod, robots.txt, serta pemeriksaan Search Console.
Peta situs adalah daftar URL publik, bukan jaminan indeks
Saat Claude Code membantu menerbitkan banyak artikel, dokumentasi, atau halaman produk, masalahnya tidak selalu ada pada templat halaman. Sering kali masalahnya adalah apakah mesin pencari dapat menemukan URL yang benar. Peta situs XML memberi tahu URL kanonis yang ingin dirayapi, kapan halaman berubah secara penting, dan bagaimana versi terjemahan saling berhubungan.
Informasi ini harus akurat. Dokumentasi Google saat ini menyatakan bahwa priority dan changefreq diabaikan, sedangkan lastmod hanya berguna jika konsisten dengan perubahan nyata. Endpoint ping lama untuk peta situs juga sudah dihentikan. Jadi alur modern sebaiknya memakai robots.txt, Google Search Console, dan pemeriksaan setelah deploy, bukan https://www.google.com/ping?sitemap=....
Panduan ini membahas dua cara praktis: integrasi resmi Astro dan generator Node.js tanpa dependensi untuk koleksi MDX multibahasa. Untuk konteks SEO yang lebih luas, lihat juga optimasi SEO dengan Claude Code dan pengaturan CI/CD dengan Claude Code.
Aturan resmi yang perlu diikuti
| Hal | Keputusan praktis |
|---|---|
| URL | Gunakan URL lengkap seperti https://example.com/blog/post/ |
| Batas | Pisahkan sebelum 50.000 URL atau 50 MB tanpa kompresi |
| Encoding | Simpan sebagai UTF-8 dan lakukan escape nilai XML |
lastmod | Pakai tanggal perubahan penting yang benar-benar terjadi |
priority / changefreq | Dapat dihilangkan untuk Google |
| Halaman multibahasa | Setiap URL mencantumkan dirinya sendiri dan semua versi bahasa lain |
| Pengiriman | Gunakan robots.txt dan Search Console; hapus skrip ping |
Sumber utamanya adalah panduan peta situs Google, pengumuman penghentian ping Google, panduan versi lokal, dan protokol sitemaps.org.
Kasus 1: halaman Astro dan rute blog
Untuk situs Astro statis, mulai dari @astrojs/sitemap. Integrasi ini berjalan saat astro build dan dapat menambahkan hubungan bahasa jika struktur rutenya jelas.
npx astro add sitemap
// astro.config.mjs
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://claudecodelab.com',
integrations: [
sitemap({
filter: (page) => !page.includes('/draft/') && !page.includes('/preview/'),
i18n: {
defaultLocale: 'ja',
locales: {
ja: 'ja',
en: 'en',
zh: 'zh-CN',
ko: 'ko',
es: 'es',
fr: 'fr',
de: 'de',
pt: 'pt-BR',
hi: 'hi',
id: 'id',
},
},
}),
],
});
Kesalahan yang sering terjadi adalah nilai site salah. Jangan biarkan localhost, domain pratinjau, atau campuran http dan https masuk ke peta situs publik. Google akan merayapi URL persis seperti yang tertulis, jadi URL tersebut harus sama dengan URL kanonis halaman.
Kasus 2: generator Node.js untuk MDX multibahasa
Generator khusus berguna saat konten berada di koleksi seperti blog, blog-en, dan blog-zh, atau saat updatedDate harus menjadi lastmod. Contoh berikut hanya memakai modul bawaan Node.js dan menulis public/sitemap.xml.
// scripts/generate-sitemap.mjs
import { mkdir, readdir, readFile, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
const SITE_URL = (process.env.SITE_URL ?? 'https://example.com').replace(/\/$/, '');
const OUT_DIR = 'public';
const OUT_FILE = path.join(OUT_DIR, 'sitemap.xml');
const collections = [
{ dir: 'site/src/content/blog', prefix: '/blog', hreflang: 'ja' },
{ dir: 'site/src/content/blog-en', prefix: '/en/blog', hreflang: 'en' },
{ dir: 'site/src/content/blog-zh', prefix: '/zh/blog', hreflang: 'zh-CN' },
{ dir: 'site/src/content/blog-ko', prefix: '/ko/blog', hreflang: 'ko' },
{ dir: 'site/src/content/blog-es', prefix: '/es/blog', hreflang: 'es' },
{ dir: 'site/src/content/blog-fr', prefix: '/fr/blog', hreflang: 'fr' },
{ dir: 'site/src/content/blog-de', prefix: '/de/blog', hreflang: 'de' },
{ dir: 'site/src/content/blog-pt', prefix: '/pt/blog', hreflang: 'pt-BR' },
{ dir: 'site/src/content/blog-hi', prefix: '/hi/blog', hreflang: 'hi' },
{ dir: 'site/src/content/blog-id', prefix: '/id/blog', hreflang: 'id' },
];
function escapeXml(value) {
return String(value).replace(/[<>&'"]/g, (char) => ({
'<': '<',
'>': '>',
'&': '&',
"'": ''',
'"': '"',
})[char]);
}
async function* walk(dir) {
let items;
try {
items = await readdir(dir, { withFileTypes: true });
} catch (error) {
if (error.code === 'ENOENT') return;
throw error;
}
for (const item of items) {
const fullPath = path.join(dir, item.name);
if (item.isDirectory()) {
yield* walk(fullPath);
} else if (/\.(md|mdx)$/.test(item.name)) {
yield fullPath;
}
}
}
function frontmatterOf(source) {
return source.match(/^---\n([\s\S]*?)\n---/)?.[1] ?? '';
}
function dateField(frontmatter, key) {
return frontmatter.match(new RegExp(`^${key}:\\s*["']?(\\d{4}-\\d{2}-\\d{2})`, 'm'))?.[1];
}
function routeSlug(collectionDir, filePath) {
return path
.relative(collectionDir, filePath)
.replace(/\\/g, '/')
.replace(/\.(md|mdx)$/, '')
.replace(/\/index$/, '');
}
function encodeRoute(slug) {
return slug.split('/').map(encodeURIComponent).join('/');
}
async function collectEntries() {
const bySlug = new Map();
for (const collection of collections) {
for await (const filePath of walk(collection.dir)) {
const source = await readFile(filePath, 'utf8');
const frontmatter = frontmatterOf(source);
if (/^draft:\s*true\s*$/m.test(frontmatter)) continue;
const info = await stat(filePath);
const slug = routeSlug(collection.dir, filePath);
const lastmod =
dateField(frontmatter, 'updatedDate') ??
dateField(frontmatter, 'pubDate') ??
info.mtime.toISOString().slice(0, 10);
const route = `${collection.prefix}/${encodeRoute(slug)}/`;
const variant = {
loc: `${SITE_URL}${route}`,
hreflang: collection.hreflang,
lastmod,
};
const variants = bySlug.get(slug) ?? [];
variants.push(variant);
bySlug.set(slug, variants);
}
}
return [...bySlug.values()].flatMap((variants) =>
variants.map((variant) => ({
...variant,
alternates: variants.map(({ hreflang, loc }) => ({ hreflang, loc })),
})),
);
}
function buildSitemap(entries) {
const urls = entries.map((entry) => ` <url>
<loc>${escapeXml(entry.loc)}</loc>
<lastmod>${entry.lastmod}</lastmod>
${entry.alternates.map((alt) => ` <xhtml:link rel="alternate" hreflang="${escapeXml(alt.hreflang)}" href="${escapeXml(alt.loc)}" />`).join('\n')}
</url>`).join('\n');
return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
${urls}
</urlset>
`;
}
const entries = await collectEntries();
if (entries.length === 0) {
throw new Error('Tidak ada URL publik untuk peta situs.');
}
await mkdir(OUT_DIR, { recursive: true });
await writeFile(OUT_FILE, buildSitemap(entries), 'utf8');
console.log(`${entries.length} URL ditulis ke ${OUT_FILE}.`);
Jalankan seperti ini:
SITE_URL=https://claudecodelab.com node scripts/generate-sitemap.mjs
Kasus 3: pisahkan artikel, produk, dan dokumentasi
Blog kecil cukup memakai satu sitemap.xml. Situs yang lebih besar sebaiknya memisahkan peta situs berdasarkan jenis konten. Ini menjaga ukuran tetap di bawah batas resmi dan memudahkan pemeriksaan di Search Console.
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://example.com/sitemap-pages.xml</loc>
<lastmod>2026-06-03</lastmod>
</sitemap>
<sitemap>
<loc>https://example.com/sitemap-blog.xml</loc>
<lastmod>2026-06-03</lastmod>
</sitemap>
<sitemap>
<loc>https://example.com/sitemap-products.xml</loc>
<lastmod>2026-06-03</lastmod>
</sitemap>
</sitemapindex>
Minta Claude Code mencatat jumlah URL per file, membagi sebelum 50.000 URL, dan memastikan indeks hanya menunjuk peta situs dari situs yang sama.
robots.txt, Search Console, dan pemeriksaan
Tambahkan peta situs ke robots.txt:
User-agent: *
Allow: /
Sitemap: https://claudecodelab.com/sitemap.xml
Lalu kirim URL tersebut sekali di Google Search Console. Dalam proses deploy, pastikan URL publik mengembalikan HTTP 200 dan terlihat seperti XML peta situs.
// scripts/verify-sitemap.mjs
const sitemapUrl = process.env.SITEMAP_URL ?? 'https://example.com/sitemap.xml';
const response = await fetch(sitemapUrl);
if (!response.ok) {
throw new Error(`Permintaan peta situs gagal: HTTP ${response.status}`);
}
const xml = await response.text();
if (!xml.includes('<urlset') && !xml.includes('<sitemapindex')) {
throw new Error('Respons tidak tampak seperti XML peta situs.');
}
console.log(`${sitemapUrl} sudah diperiksa. Ukuran: ${xml.length} bytes`);
Kesalahan yang harus dicegah sebelum publikasi
Kesalahan pertama adalah membuat semua lastmod sama dengan tanggal build. Jika isi halaman tidak berubah, tanggalnya juga tidak perlu berubah.
Kesalahan kedua adalah memasukkan draf, halaman noindex, sumber pengalihan, atau URL duplikat. Peta situs hanya berisi URL kanonis yang ingin muncul di hasil pencarian.
Kesalahan ketiga adalah hubungan hreflang satu arah. Setiap versi bahasa harus menunjuk dirinya sendiri dan semua versi bahasa lain.
Kesalahan keempat adalah lupa escape XML. Tanda & dalam query string harus menjadi &.
Monetisasi dan hasil praktik
Peta situs tidak langsung menghasilkan pendapatan, tetapi menjaga jalur penemuan untuk halaman yang bernilai: tutorial, perbandingan, materi gratis, dan halaman konsultasi. Setelah peta situs diperbaiki, periksa tautan internal dan CTA agar pembaca dapat bergerak alami ke pelatihan Claude Code atau sumber terkait.
Dalam alur ClaudeCodeLab milik Masa, perubahan paling terasa adalah menghapus ping lama, menyamakan lastmod dengan updatedDate, dan menghubungkan sepuluh versi bahasa dengan hreflang timbal balik. Peninjauan editorial menjadi lebih jelas karena peta situs memantulkan tanggal dan slug yang sama dengan frontmatter MDX.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Workflow Obsidian ke CLAUDE.md untuk Claude Code
Ubah catatan kerja Obsidian menjadi operating note CLAUDE.md agar konteks tidak dijelaskan ulang.
Claude Code Revenue CTA Routing: dari artikel ke PDF, Gumroad, dan konsultasi
Workflow Claude Code untuk mengarahkan pembaca ke PDF gratis, Gumroad, atau konsultasi sesuai intent.
Aturan handoff tim Claude Code: bukti review, permission, rollback, dan jalur revenue
Format handoff Claude Code untuk tim: bukti, permission rule, rollback, PDF gratis, Gumroad, dan konsultasi.