Panduan Lengkap Membuat Sitemap XML Premium Ala Yoast SEO di Next.js (App Router)

PE
Pena Edukasi
XML Sitemap website Pena Edukasi

XML Sitemap Premium

Sitemap XML adalah peta navigasi krusial yang digunakan oleh mesin pencari seperti Googlebot untuk menemukan, memahami, dan mengindeks seluruh halaman di website Anda. Seiring bertambahnya jumlah konten, menggabungkan ratusan atau ribuan URL ke dalam satu file tunggal sitemap.xml bukanlah praktik yang disarankan karena rentan melampaui batas ukuran maksimal dari Google.

Standar industri SEO profesional—seperti yang diterapkan oleh plugin premium WordPress seperti Yoast SEO—selalu menggunakan arsitektur Sitemap Index. Metode ini memecah sitemap utama menjadi beberapa sub-sitemap spesifik, misalnya khusus untuk halaman (pages), artikel (posts), kategori (categories), dan label (tags).

Dalam panduan ini, kita akan membangun sistem Sitemap Index yang sepenuhnya dinamis menggunakan Next.js (App Router). Tidak hanya berfungsi secara teknis, kita juga akan menyematkan desain antarmuka berbasis XSLT agar sitemap Anda tidak tampil sebagai kode mentah yang membingungkan, melainkan menjadi tabel visual yang rapi, bisa diklik, dan mengusung branding eksklusif milik website Anda sendiri.

Alur Kerja dan Arsitektur Sistem

Sebelum mulai menulis kode, mari pahami konsep dasar dari arsitektur yang akan kita bangun:

  1. Desain Visual (XSLT): Kita akan membuat sebuah file stylesheet yang bertugas menyulap baris kode XML menjadi tabel HTML yang interaktif di browser.
  2. Route Handlers Dinamis: Kita akan memanfaatkan fitur route.ts di Next.js untuk merender sitemap secara langsung.
  3. Optimasi Performa (Edge & Cache): Semua sitemap akan dikonfigurasi menggunakan runtime: 'edge' agar berjalan super cepat di infrastruktur serverless, serta dilengkapi sistem cache agar tidak membebani database (CMS) Anda saat Googlebot melakukan crawling massal.

Catatan: Pada panduan ini, kita akan menggunakan fungsi fetch() untuk menarik data. Anda cukup menyesuaikan URL API pada kode di bawah dengan endpoint backend atau Headless CMS (seperti Strapi, Sanity, atau Cloudflare Worker) yang Anda gunakan.

Langkah 1: Membuat Desain Antarmuka Premium (XSLT)

Inilah rahasia mengapa sitemap dari Yoast SEO terlihat sangat rapi dan mudah dibaca oleh manusia. Kita akan membuat file XSLT (eXtensible Stylesheet Language Transformations).

Buat file baru di dalam folder public/ proyek Next.js Anda dengan nama sitemap-style.xsl. Salin dan tempel kode berikut ke dalamnya:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
    xmlns:html="http://www.w3.org/TR/REC-html40"
    xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
    xmlns:sitemap="http://www.sitemaps.org/schemas/sitemap/0.9"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:template match="/">
        <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
            <title>XML Sitemap</title>
            <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
            <style type="text/css">
                body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; color: #444; margin: 0; padding: 20px; font-size: 13px; }
                #content { max-width: 960px; margin: 0 auto; }
                h1 { color: #333; font-size: 24px; font-weight: bold; margin-bottom: 10px; }
                p { margin-top: 0; line-height: 1.5; color: #555; }
                a { color: #2563eb; text-decoration: none; }
                a:hover { text-decoration: underline; }
                .brand-name { color: #d63638; font-weight: bold; }
                table { width: 100%; border-collapse: collapse; margin-top: 15px; font-size: 13px; }
                /* Padding dibuat rapat (4px) agar presisi seperti desain premium */
                th { text-align: left; padding: 4px 10px; border-bottom: 2px solid #ddd; font-weight: bold; color: #333; }
                td { padding: 4px 10px; border-bottom: 1px solid #eee; }
                tr:nth-child(even) td { background-color: #f9f9f9; }
                td a { color: #333; }
                td a:hover { color: #2563eb; }
            </style>
        </head>
        <body>
            <div id="content">
                <h1>XML Sitemap</h1>
                <!-- Silakan ganti "Nama Brand SEO" dengan nama website Anda -->
                <p>Generated by <span class="brand-name">Nama Brand SEO</span>, this is an XML Sitemap, meant for consumption by search engines.</p>
                
                <xsl:choose>
                    <!-- Logika ketika membaca file Index Utama -->
                    <xsl:when test="sitemap:sitemapindex">
                        <p>This XML Sitemap Index file contains <xsl:value-of select="count(sitemap:sitemapindex/sitemap:sitemap)"/> sitemaps.</p>
                        <table>
                            <thead><tr><th>Sitemap</th><th>Last Modified</th></tr></thead>
                            <tbody>
                                <xsl:for-each select="sitemap:sitemapindex/sitemap:sitemap">
                                    <tr>
                                        <td><a href="{sitemap:loc}"><xsl:value-of select="sitemap:loc"/></a></td>
                                        <td><xsl:value-of select="sitemap:lastmod"/></td>
                                    </tr>
                                </xsl:for-each>
                            </tbody>
                        </table>
                    </xsl:when>
                    
                    <!-- Logika ketika membaca file Sub-Sitemap (Post, Page, dll) -->
                    <xsl:when test="sitemap:urlset">
                        <p>This XML Sitemap contains <xsl:value-of select="count(sitemap:urlset/sitemap:url)"/> URLs.</p>
                        <table>
                            <thead><tr><th>URL</th><th>Last Modified</th><th>Change Freq</th><th>Priority</th></tr></thead>
                            <tbody>
                                <xsl:for-each select="sitemap:urlset/sitemap:url">
                                    <tr>
                                        <td><a href="{sitemap:loc}"><xsl:value-of select="sitemap:loc"/></a></td>
                                        <td><xsl:value-of select="sitemap:lastmod"/></td>
                                        <td><xsl:value-of select="sitemap:changefreq"/></td>
                                        <td><xsl:value-of select="sitemap:priority"/></td>
                                    </tr>
                                </xsl:for-each>
                            </tbody>
                        </table>
                    </xsl:when>
                </xsl:choose>
            </div>
        </body>
        </html>
    </xsl:template>
</xsl:stylesheet>

Logika <xsl:choose> di atas akan mendeteksi apakah browser sedang membuka sitemap utama atau sub-sitemap, lalu menyesuaikan jumlah kolom secara otomatis.

Baca Juga: Adab kepada Orang Tua dan Guru: Dua Pintu Keberkahan dalam Hidup

Langkah 2: Membuat Sub-Sitemaps Spesifik

Selanjutnya, kita akan membuat 4 sub-sitemap terpisah. Anda perlu membuat folder dan file route.ts di dalam direktori app/ untuk masing-masing bagian. Jangan lupa untuk menambahkan tag penghubung <?xml-stylesheet type="text/xsl" href="/sitemap-style.xsl"?> di setiap kodenya.

A. Pages Sitemap (Untuk Halaman Statis)

Buat struktur direktori: app/pages-sitemap.xml/route.ts

File ini akan menampung halaman-halaman inti dari website Anda seperti Beranda, Kontak, atau Kebijakan Privasi yang jarang berubah.

import { NextResponse } from 'next/server';
export const runtime = 'edge';

export async function GET() {
  const baseUrl = 'https://domainanda.com';
  const now = new Date().toISOString();

  const xml = `<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/sitemap-style.xsl"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>${baseUrl}</loc><lastmod>${now}</lastmod><changefreq>yearly</changefreq><priority>1.0</priority>
  </url>
  <url>
    <loc>${baseUrl}/p/kontak</loc><lastmod>${now}</lastmod><changefreq>yearly</changefreq><priority>0.8</priority>
  </url>
  <url>
    <loc>${baseUrl}/p/tentang-kami</loc><lastmod>${now}</lastmod><changefreq>monthly</changefreq><priority>0.8</priority>
  </url>
</urlset>`;

  return new NextResponse(xml, { headers: { 'Content-Type': 'application/xml; charset=utf-8' } });
}

B. Posts Sitemap (Untuk Artikel Dinamis)

Buat struktur direktori: app/posts-sitemap.xml/route.ts

Kode ini akan menarik daftar artikel yang sudah dirilis (published) dari API CMS Anda secara dinamis.

import { NextResponse } from 'next/server';
export const runtime = 'edge';

export async function GET() {
  const baseUrl = 'https://domainanda.com';
  
  try {
    // Sesuaikan URL ini dengan API Endpoint backend Anda
    const res = await fetch('https://api-cms-anda.com/articles?status=published', {
      next: { revalidate: 3600 } // Cache data selama 1 jam agar server tidak kelebihan beban
    });

    const json = await res.json();
    const articles = json.data || [];
    
    let xml = `<?xml version="1.0" encoding="UTF-8"?>\n<?xml-stylesheet type="text/xsl" href="/sitemap-style.xsl"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n`;
    
    articles.forEach((post: any) => {
      const lastMod = new Date(post.created_at || Date.now()).toISOString();
      xml += `  <url>\n    <loc>${baseUrl}/post/${post.slug}</loc>\n    <lastmod>${lastMod}</lastmod>\n    <changefreq>weekly</changefreq>\n    <priority>0.7</priority>\n  </url>\n`;
    });

    xml += `</urlset>`;
    return new NextResponse(xml, { headers: { 'Content-Type': 'application/xml; charset=utf-8' } });
  } catch (error) { return new NextResponse('Error generating posts sitemap', { status: 500 }); }
}

Baca Juga: Cara Generate dan Verifikasi API Key IndexNow di Bing Webmaster Tools

C. Categories Sitemap

Buat struktur direktori: app/categories-sitemap.xml/route.ts

File ini membaca seluruh artikel yang didapat dari fetch API, kemudian mengekstraksi kategorinya secara otomatis dan menghapus data yang duplikat.

import { NextResponse } from 'next/server';
export const runtime = 'edge';

export async function GET() {
  const baseUrl = 'https://domainanda.com';

  try {
    // Gunakan fetch API ke backend Anda di sini
    const res = await fetch('https://api-cms-anda.com/articles?status=published', { next: { revalidate: 3600 } });
    const json = await res.json();
    const articles = json.data || [];

    // Logika menghapus duplikasi kategori
    const uniqueCategories = Array.from(
      new Set(articles.map((post: any) => post.category).filter(Boolean))
    ) as string[];

    let xml = `<?xml version="1.0" encoding="UTF-8"?>\n<?xml-stylesheet type="text/xsl" href="/sitemap-style.xsl"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n`;

    uniqueCategories.forEach((cat) => {
      const slug = cat.toLowerCase().replace(/[^a-z0-9]+/g, '-');
      xml += `  <url>\n    <loc>${baseUrl}/category/${slug}</loc>\n    <lastmod>${new Date().toISOString()}</lastmod>\n    <changefreq>weekly</changefreq>\n    <priority>0.8</priority>\n  </url>\n`;
    });

    xml += `</urlset>`;
    return new NextResponse(xml, { headers: { 'Content-Type': 'application/xml; charset=utf-8' } });
  } catch (error) { return new NextResponse('Error generating categories', { status: 500 }); }
}

D. Tags Sitemap (Pemisahan Label Otomatis)

Buat struktur direktori: app/tags-sitemap.xml/route.ts

Biasanya, label (tags) disimpan dalam database dalam bentuk string yang digabung dengan koma (contoh: "SEO, Web Design, Nextjs"). Kode berikut akan memecahnya menjadi URL individual secara otomatis.

import { NextResponse } from 'next/server';
export const runtime = 'edge';

export async function GET() {
  const baseUrl = 'https://domainanda.com';

  try {
    const res = await fetch('https://api-cms-anda.com/articles?status=published', { next: { revalidate: 3600 } });
    const json = await res.json();
    const articles = json.data || [];

    // Logika memisahkan tag berdasarkan koma
    const allTags = new Set<string>();
    articles.forEach((post: any) => {
      if (post.tags) {
        const tagArray = post.tags.split(',').map((t: string) => t.trim()).filter(Boolean);
        tagArray.forEach((t: string) => allTags.add(t));
      }
    });
    
    const uniqueTags = Array.from(allTags);
    let xml = `<?xml version="1.0" encoding="UTF-8"?>\n<?xml-stylesheet type="text/xsl" href="/sitemap-style.xsl"?>\n<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n`;
    
    uniqueTags.forEach((tag) => {
      const slug = tag.toLowerCase().replace(/[^a-z0-9]+/g, '-');
      xml += `  <url>\n    <loc>${baseUrl}/tag/${slug}</loc>\n    <lastmod>${new Date().toISOString()}</lastmod>\n    <changefreq>weekly</changefreq>\n    <priority>0.6</priority>\n  </url>\n`;
    });

    xml += `</urlset>`;
    return new NextResponse(xml, { headers: { 'Content-Type': 'application/xml; charset=utf-8' } });
  } catch (error) { return new NextResponse('Error generating tags', { status: 500 }); }
}

Baca Juga: Cara Otomatisasi IndexNow Bing di Next.js dan Cloudflare Pages CMS

Langkah 3: Merangkai Sitemap Index Utama

Tahap final adalah menyatukan keempat sitemap yang sudah kita buat ke dalam satu pintu gerbang utama. File inilah yang nantinya akan didaftarkan ke Google.

Buat file: app/sitemap.xml/route.ts

import { NextResponse } from 'next/server';
export const runtime = 'edge';

export async function GET() {
  const baseUrl = 'https://domainanda.com';
  const now = new Date().toISOString(); 
  
  const xml = `<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/sitemap-style.xsl"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap><loc>${baseUrl}/posts-sitemap.xml</loc><lastmod>${now}</lastmod></sitemap>
  <sitemap><loc>${baseUrl}/pages-sitemap.xml</loc><lastmod>${now}</lastmod></sitemap>
  <sitemap><loc>${baseUrl}/categories-sitemap.xml</loc><lastmod>${now}</lastmod></sitemap>
  <sitemap><loc>${baseUrl}/tags-sitemap.xml</loc><lastmod>${now}</lastmod></sitemap>
</sitemapindex>`;

  return new NextResponse(xml, { headers: { 'Content-Type': 'application/xml; charset=utf-8' } });
}

Langkah 4: Deployment dan Pengecekan

Simpan seluruh file Anda, lalu lakukan commit dan push proyek Next.js Anda ke platform hosting (seperti Vercel atau Cloudflare Pages).

Setelah proses deploy selesai:

  1. Buka URL https://domainanda.com/sitemap.xml di browser.
  2. Jika Anda masih melihat XML versi lama atau tampilan teks mentah tanpa styling, lakukan Hard Refresh dengan menekan Ctrl + F5 (Windows) atau Cmd + Shift + R (Mac) untuk membersihkan cache browser.

Jika semuanya berjalan lancar, Anda akan melihat sitemap yang berformat tabel cantik dengan merek (brand) Anda sendiri.


🚀 Lihat Live Demo Hasil Akhirnya!

Ingin tahu seperti apa tampilan sitemap premium ini saat sudah di-deploy secara nyata di tahap produksi? Anda dapat melihat contoh sukses penerapan kode di atas secara langsung pada sitemap Pena Edukasi melalui tautan berikut:


👉 Live Demo Sitemap XML Pena Edukasi

Langkah mutlak yang terakhir adalah menyerahkan peta situs Anda ke mesin pencari. Masuk ke dashboard Google Search Console, navigasi ke menu Peta Situs (Sitemaps), dan masukkan URL sitemap.xml. Google akan membaca file utama ini dan secara otomatis menemukan URL sub-sitemap Anda (posts, pages, categories, tags) dengan sangat terstruktur.

Dengan implementasi di atas, website Next.js Anda kini memiliki arsitektur SEO enterprise-grade tingkat lanjut yang siap bersaing di halaman pertama mesin pencari. Selamat mencoba!

H
Verified Author Pena Edukasi

Hayatul Makki

Kontributor resmi dan penulis di Pena Edukasi yang aktif membagikan informasi, materi, serta wawasan mendalam seputar dunia pendidikan digital.

Artikel Terkait