Cara Membuat Breadcrumb Kustom & Schema di WordPress

Kali ini, saya akan membuat breadcrumb di WordPress. Website ini menggunakan Custom Post Types (CPT) untuk memisahkan konten dan plugin multi-bahasa WPML.

Masalah lainnya plugin All in One SEO (AIOSEO) menghasilkan breadcrumb yang keliru. Alih-alih menampilkan struktur Home > Nama Custom Post Types > Nama Halaman, yang muncul malah Home > Nama Halaman. Tentu saja ini tidak ideal untuk user experience maupun SEO.

Artikel ini adalah catatan lapangan atau dokumentasi proyek. Saya akan membedah setiap fase, mulai dari fondasi kode, iterasi perbaikan, hingga penyelesaian konflik teknis yang tak terduga.

Fase 1: Membuat Shortcode Breadcrumb Dasar

Tugas pertama adalah membuat fondasi fondasi sebuah fungsi dinamis di WordPress yang bisa dipanggil di mana saja dengan mudah.

Eksekusi:

  • Saya membuat fungsi PHP bernama get_custom_breadcrumb() di dalam file functions.php tema. Fungsi ini bertugas merangkai struktur HTML breadcrumb.
  • Fungsi tersebut kemudian saya daftarkan sebagai shortcode [custom_breadcrumb]. Ini memungkinkan saya untuk menempatkan breadcrumb di editor mana pun.
  • Terakhir, saya implementasikan CSS yang sudah ada untuk memastikan tampilannya sesuai desain.

Secara visual, breadcrumb berhasil tampil. Namun, ini baru permulaan. Fungsi dasarnya belum cukup pintar untuk mengenali struktur website yang kompleks.

Fase 2: Mengatasi Masalah Custom Post Type (CPT)

Di sinilah masalah pertama muncul. Saat saya membuka halaman detail “Litecoin” (yang merupakan bagian dari CPT “Kripto”), breadcrumb-nya tidak menampilkan “Kripto” sebagai induknya, hanya Home > Litecoin.

hasil pertama breadcrumb

Saya sadar fungsi awal hanya dirancang untuk Post dan Page standar dan tidak tahu cara mengidentifikasi CPT.

Solusi yang Diterapkan:

  • Saya membuat sebuah “peta” sederhana di dalam fungsi menggunakan array PHP, $cpt_page_map. Peta ini menghubungkan slug CPT (misal: ‘crypto’) dengan ID Halaman kustom yang menjadi induknya.
  • Fungsi breadcrumb kemudian saya modifikasi untuk memeriksa CPT dari halaman yang sedang dibuka. Jika CPT-nya ada di dalam peta, fungsi akan mengambil judul dan link dari Halaman kustom tersebut untuk ditampilkan sebagai induk.

Problem solved! Struktur breadcrumb untuk bahasa utama (Bahasa Indonesia) kini sudah benar.

Fase 3: Integrasi Multi-bahasa (WPML)

Di halaman versi Inggris, breadcrumb masih menampilkan nama induk dalam Bahasa Indonesia. Contohnya, Home > Aset Kripto > Litecoin, padahal seharusnya Home > Crypto > Litecoin.

Hal ini Ini terjadi karena peta $cpt_page_map menggunakan ID Halaman yang statis. ID tersebut tentu saja merujuk ke halaman versi bahasa default (Indonesia).

Solusi yang Diterapkan:

  • Solusinya adalah membuat fungsi ini “sadar bahasa”. Saya mengintegrasikan fungsi bawaan WPML, yaitu icl_object_id().
  • Fungsi ini sangat powerful. Ia mengambil ID dari halaman bahasa default, lalu secara otomatis mencari dan mengembalikan ID halaman terjemahannya sesuai bahasa yang sedang aktif di website.

Breadcrumb kini sepenuhnya fungsional dan akurat di semua bahasa. Tantangan struktur dan bahasa berhasil diatasi.

Fase 4: Implementasi Schema Markup Breadcrumb

Membuat breadcrumb yang fungsional itu satu hal, tapi membuatnya “berbicara” dengan Google adalah hal lain. Di sinilah schema markup breadcrumb berperan.

Oleh sebab itu, saya harus menambahkan structured data (data terstruktur) agar Google bisa menampilkan breadcrumb di hasil pencarian, yang berpotensi meningkatkan CTR.

Eksekusi:

  • Saya memodifikasi lagi fungsi get_custom_breadcrumb. Kali ini, selain menghasilkan HTML, fungsi ini juga secara bersamaan membuat array data untuk schema.
  • Array data ini kemudian diformat menjadi JSON-LD, format yang direkomendasikan Google, dan disisipkan dalam tag <script> tepat setelah HTML breadcrumb.
  • Saya memastikan setiap item memiliki properti position, name, dan item (URL) sesuai standar schema.org.

Saya menggunakan Google Rich Results Test untuk memvalidasi URL. Ini wajib untuk memastikan tidak ada error yang bisa membuat Google mengabaikan schema kita.

Beberapa orang bertanya apakah menempatkan JSON-LD di <body> itu buruk untuk SEO. Jawabannya tidak sama sekali. Google secara eksplisit menyatakan script JSON-LD bisa diletakkan di <head> atau <body> tanpa masalah.

Sekarang statusnya breadcrumb sudah aktif dan kini tidak hanya membantu pengguna, tapi juga dioptimalkan untuk mesin pencari.

Fase 5: Menyelesaikan Konflik Duplikasi Schema AIOSEO

Saat validasi, saya menemukan masalah terakhir. Ada dua schema BreadcrumbList di halaman yang sama. Satu dari kode kustom saya (yang benar), dan satu lagi dari AIOSEO (yang salah). Ini bisa membingungkan Google.

Plugin AIOSEO secara default tetap menghasilkan schema breadcrumb, bahkan jika kita tidak menggunakan fitur visualnya.

Solusi yang Diterapkan:

  • Opsi 1 (UI Plugin): Cara termudah adalah masuk ke pengaturan AIOSEO dan menonaktifkan modul Breadcrumbs. Ini adalah solusi yang paling bersih.
  • Opsi 2 (Kode): Saya ingin tetap fleksibel, jadi saya memilih jalur kode. Saya perlu cara untuk “mencegat” output schema AIOSEO dan menghapus bagian breadcrumb saja.
  • Catatan Proses: Saya mencoba beberapa filter PHP seperti aioseo_schema_graph dan aioseo_schema_disable_breadcrumb namun tidak berhasil. Setelah riset lebih dalam, filter yang akhirnya bekerja adalah aioseo_schema_output.

Hasil Akhir

Pada akhirnya, saya berhasil membuat breadcrumb kustom yang sepenuhnya dinamis, akurat secara struktural, mendukung multi-bahasa, dan memiliki schema markup breadcrumb yang valid tanpa duplikasi.

hasil akhir bahasa indonesia
hasil akhir bahasa inggris

Berikut adalah kode final yang saya gunakan di functions.php WordPress:

/* ================================== */
/* BREADCRUMB KUSTOM START            */
/* ================================== */

/**
 * Membuat shortcode [custom_breadcrumb] yang menghasilkan HTML
 * dan Schema Markup JSON-LD yang ramah SEO dan WPML.
 */
function get_custom_breadcrumb() {
    // ---- PETA CPT KE HALAMAN (ANDA HANYA PERLU MENGEDIT BAGIAN INI) ----
    // Pastikan ID di bawah ini adalah ID dari Halaman dalam BAHASA DEFAULT Anda
    $cpt_page_map = array(
        'crypto'       => 1, // ID Halaman "Kripto"
        'stocks'       => 2, // ID Halaman "Stocks"
    );
    // --------------------------------------------------------------------

    // Inisialisasi untuk output HTML dan Schema
    $html_items = [];
    $schema_items = [];
    $position = 1;

    // --- Item 1: Home ---
    $home_page_id = get_option('page_on_front');
    if ( function_exists('icl_object_id') ) {
        $home_page_id = icl_object_id($home_page_id, 'page', true);
    }
    $home_link = get_permalink($home_page_id);
    $home_text = get_the_title($home_page_id) ? get_the_title($home_page_id) : 'Home';

    $html_items[] = '<li class="breadcrumb-item"><a href="' . $home_link . '">' . $home_text . '</a></li>';
    $schema_items[] = array(
        '@type' => 'ListItem',
        'position' => $position,
        'name' => $home_text,
        'item' => $home_link
    );
    $position++;

    // --- Item 2 dan seterusnya ---
    if ( is_singular() ) {
        global $post;
        $post_type = get_post_type( $post );

        if ( array_key_exists( $post_type, $cpt_page_map ) ) {
            $default_lang_page_id = $cpt_page_map[$post_type];
            $translated_page_id = $default_lang_page_id;
            if ( function_exists('icl_object_id') ) {
                $translated_page_id = icl_object_id( $default_lang_page_id, 'page', true );
            }
            $parent_link = get_permalink( $translated_page_id );
            $parent_title = get_the_title( $translated_page_id );
            
            $html_items[] = '<li class="breadcrumb-item"><a href="' . $parent_link . '">' . $parent_title . '</a></li>';
            $schema_items[] = array(
                '@type' => 'ListItem',
                'position' => $position,
                'name' => $parent_title,
                'item' => $parent_link
            );
            $position++;
        }
    }
    
    // --- Item Terakhir (Halaman Aktif) ---
    $current_title = get_the_title();
    $html_items[] = '<li class="breadcrumb-item active" aria-current="page">' . $current_title . '</li>';
    $schema_items[] = array(
        '@type' => 'ListItem',
        'position' => $position,
        'name' => $current_title
    );

    // --- Bangun Output Final ---
    $output = '<div class="breadcrumb-container">';
    $output .= '<nav aria-label="breadcrumb">';
    $output .= '<ol class="breadcrumb">' . implode('', $html_items) . '</ol>';
    $output .= '</nav>';
    $output .= '</div>';

    $schema = array(
        '@context' => '[https://schema.org](https://schema.org)',
        '@type' => 'BreadcrumbList',
        'itemListElement' => $schema_items
    );
    $output .= '<script type="application/ld+json">' . json_encode($schema, JSON_UNESCAPED_SLASHES) . '</script>';

    return $output;
}
add_shortcode('custom_breadcrumb', 'get_custom_breadcrumb');

/**
 * Menghapus Schema Markup BreadcrumbList dari AIOSEO untuk menghindari duplikasi.
 */
add_filter( 'aioseo_schema_output', 'aioseo_filter_schema_output' );
function aioseo_filter_schema_output( $graphs ) {
    foreach ( $graphs as $index => $graph ) {
        if ( isset($graph['@type']) && 'BreadcrumbList' === $graph['@type'] ) {
            unset( $graphs[ $index ] );
        }
        // Juga periksa kunci 'breadcrumb' di dalam tipe lain
        if (is_array($graph)) {
            foreach ( $graph as $key => $value ) {
                if ( 'breadcrumb' === $key ) {
                    unset( $graphs[ $index ][ $key ] );
                }
            }
        }
    }
    return $graphs;
}

/* ================================== */
/* BREADCRUMB KUSTOM END              */
/* ================================== */

Pelajaran dari Proses:

  • Keterbatasan Plugin: Plugin “All-in-One” sekalipun tidak selalu bisa mengakomodasi setiap struktur website yang unik.
  • Pentingnya Iterasi: Solusi teknis jarang sekali langsung berhasil. Prosesnya selalu melibatkan identifikasi masalah, pengujian, dan perbaikan berulang kali. It’s part of the game.
  • Filter adalah Kunci: Saat berhadapan dengan plugin besar, hooks dan filters adalah cara terbaik untuk memodifikasi output tanpa menyentuh kode inti plugin.
  • Validasi Bukan Pilihan: Selalu, selalu validasi implementasi teknis (terutama schema) dengan alat resmi seperti Google Rich Results Test. Jangan hanya percaya “kayaknya udah bener”.

Intinya jangan takut dengan kode. Terkadang, tantangan on-page yang paling kompleks hanya bisa diselesaikan dengan solusi yang kita bangun sendiri.

Published by rendyandriyanto

Your go-to SEO Specialist. Passionate about achieving growth in digital marketing and financial markets.

Leave a comment

Your email address will not be published. Required fields are marked *