• Skip to secondary menu
  • Skip to main content
  • Skip to primary sidebar
  • Home
  • Projects
  • Products
  • Themes
  • Tools
  • Request for Quote

Vengala Vinay

Having 12+ Years of Experience in Software Development

  • Home
  • WordPress
  • PHP
    • Codeigniter
  • Django
  • Magento
  • Selenium
  • Server
Home » Step-by-Step Guide to building a custom XML sitemap generator block for Gutenberg using custom WebAssembly modules

Step-by-Step Guide to building a custom XML sitemap generator block for Gutenberg using custom WebAssembly modules

Leveraging WebAssembly for High-Performance XML Sitemap Generation in Gutenberg

Traditional XML sitemap generation in WordPress often relies on PHP, which can become a bottleneck for large sites. This guide details building a custom Gutenberg block that offloads the computationally intensive task of sitemap generation to a WebAssembly (Wasm) module, offering significant performance gains and a more responsive user experience within the WordPress admin. We’ll cover the Wasm module development, integration with a custom Gutenberg block, and the necessary PHP backend for WordPress.

I. Developing the WebAssembly Sitemap Generator

We’ll use Rust for its strong typing, memory safety, and excellent tooling for Wasm compilation. The core logic will involve iterating through WordPress post types and generating the XML structure.

A. Project Setup and Dependencies

Initialize a new Rust project and add the necessary Wasm-bindgen dependencies.

  • Install Rust: If you don’t have Rust installed, follow the instructions at rustup.rs.
  • Create a new library project:
cargo new --lib wasm_sitemap_generator
cd wasm_sitemap_generator

Edit your Cargo.toml to include wasm-bindgen and serde for serialization/deserialization.

[package]
name = "wasm_sitemap_generator"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
wasm-bindgen = "0.2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
chrono = { version = "0.4", features = ["serde"] }

B. Defining Data Structures

We need structures to represent the data we’ll receive from WordPress (post types, posts) and the final sitemap XML. We’ll use serde for easy JSON serialization/deserialization.

use wasm_bindgen::prelude::*;
use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc};

#[derive(Serialize, Deserialize)]
pub struct PostData {
    pub id: u64,
    pub url: String,
    pub modified_gmt: String, // Storing as string for simplicity, parse in Rust if needed
    pub post_type: String,
    pub priority: Option,
    pub changefreq: Option,
}

#[derive(Serialize, Deserialize)]
pub struct SitemapConfig {
    pub site_url: String,
    pub post_types: Vec,
    pub default_priority: f32,
    pub default_changefreq: String,
}

#[derive(Serialize)]
pub struct SitemapUrl {
    pub loc: String,
    pub lastmod: String,
    pub priority: f32,
    pub changefreq: String,
}

#[derive(Serialize)]
pub struct Sitemap {
    pub urls: Vec,
}

C. Implementing the Sitemap Generation Logic

The core function will accept a JSON string representing post data and configuration, and return a JSON string of the sitemap XML structure.

#[wasm_bindgen]
pub fn generate_sitemap(config_json: &str, posts_json: &str) -> Result {
    let config: SitemapConfig = serde_json::from_str(config_json)
        .map_err(|e| JsValue::from_str(&format!("Failed to parse config: {}", e)))?;

    let posts: Vec = serde_json::from_str(posts_json)
        .map_err(|e| JsValue::from_str(&format!("Failed to parse posts: {}", e)))?;

    let mut sitemap_urls = Vec::new();

    for post in posts {
        if !config.post_types.contains(&post.post_type) {
            continue;
        }

        // Attempt to parse the modified_gmt string into a DateTime object
        let lastmod_dt = DateTime::parse_from_rfc3339(&post.modified_gmt)
            .map_err(|e| JsValue::from_str(&format!("Failed to parse date '{}': {}", post.modified_gmt, e)))?
            .with_timezone(&Utc);

        let sitemap_url = SitemapUrl {
            loc: post.url,
            lastmod: lastmod_dt.format("%Y-%m-%dT%H:%M:%S%z").to_string(),
            priority: post.priority.unwrap_or(config.default_priority),
            changefreq: post.changefreq.unwrap_or(config.default_changefreq.clone()),
        };
        sitemap_urls.push(sitemap_url);
    }

    let sitemap = Sitemap { urls: sitemap_urls };

    serde_json::to_string(&sitemap)
        .map_err(|e| JsValue::from_str(&format!("Failed to serialize sitemap: {}", e)))
}

D. Compiling to WebAssembly

Use wasm-pack to build the Wasm module. Ensure you have it installed: cargo install wasm-pack.

wasm-pack build --target web --out-dir ../../public/wasm

This command compiles the Rust code into a WebAssembly module and generates JavaScript bindings, placing them in the ../../public/wasm directory (relative to your Rust project root). Adjust the output path as needed for your WordPress plugin structure.

II. Creating the Gutenberg Block

We’ll create a custom Gutenberg block that interacts with the Wasm module. This involves setting up the block’s JavaScript/React components and the PHP registration.

A. Block Registration (PHP)

Register the block using WordPress’s plugin API. This will enqueue the necessary JavaScript and Wasm files.

/**
 * Plugin Name: Custom Sitemap Generator Block
 * Description: A Gutenberg block for generating XML sitemaps using WebAssembly.
 * Version: 1.0.0
 * Author: Your Name
 */

if ( ! defined( 'ABSPATH' ) ) {
    exit; // Exit if accessed directly.
}

function custom_sitemap_block_register() {
    $asset_file = include( plugin_dir_path( __FILE__ ) . 'build/index.asset.php');

    wp_register_script(
        'custom-sitemap-block-editor-script',
        plugin_dir_url( __FILE__ ) . 'build/index.js',
        $asset_file['dependencies'],
        $asset_file['version']
    );

    wp_register_script(
        'wasm-sitemap-generator',
        plugin_dir_url( __FILE__ ) . 'public/wasm/wasm_sitemap_generator_bg.js', // Path to the JS bindings
        array(),
        '1.0.0',
        true // Load in footer
    );

    wp_localize_script( 'custom-sitemap-block-editor-script', 'customSitemapBlock', array(
        'wasmModuleUrl' => plugin_dir_url( __FILE__ ) . 'public/wasm/wasm_sitemap_generator_bg.wasm', // Path to the .wasm file
        'ajax_url' => admin_url( 'admin-ajax.php' ),
        'nonce'    => wp_create_nonce( 'custom_sitemap_nonce' ),
    ));

    register_block_type( 'custom-sitemap/generator', array(
        'editor_script' => 'custom-sitemap-block-editor-script',
        'editor_style'  => 'custom-sitemap-block-editor-style', // If you have one
        'render_callback' => 'custom_sitemap_block_render_callback',
    ) );
}
add_action( 'init', 'custom_sitemap_block_register' );

// Optional: Render callback for frontend display if needed
function custom_sitemap_block_render_callback( $attributes ) {
    // This block is primarily for the admin interface.
    // If you need frontend rendering, implement it here.
    return '';
}

// AJAX handler for fetching posts
function custom_sitemap_fetch_posts_handler() {
    check_ajax_referer( 'custom_sitemap_nonce', 'nonce' );

    $post_types = isset( $_POST['post_types'] ) ? array_map( 'sanitize_text_field', $_POST['post_types'] ) : array();
    $posts_per_page = isset( $_POST['posts_per_page'] ) ? intval( $_POST['posts_per_page'] ) : 100;
    $paged = isset( $_POST['paged'] ) ? intval( $_POST['paged'] ) : 1;

    $args = array(
        'post_type'      => $post_types,
        'posts_per_page' => $posts_per_page,
        'paged'          => $paged,
        'post_status'    => 'publish',
        'orderby'        => 'modified',
        'order'          => 'DESC',
    );

    $query = new WP_Query( $args );
    $posts_data = array();

    if ( $query->have_posts() ) {
        while ( $query->have_posts() ) {
            $query->the_post();
            $posts_data[] = array(
                'id'           => get_the_ID(),
                'url'          => get_permalink(),
                'modified_gmt' => get_post_modified_time( 'c', true ), // RFC 3339 format
                'post_type'    => get_post_type(),
                'priority'     => null, // Placeholder for custom logic
                'changefreq'   => null, // Placeholder for custom logic
            );
        }
        wp_reset_postdata();
    }

    wp_send_json_success( array(
        'posts' => $posts_data,
        'max_pages' => $query->max_num_pages,
    ) );
}
add_action( 'wp_ajax_custom_sitemap_fetch_posts', 'custom_sitemap_fetch_posts_handler' );

// AJAX handler for generating the sitemap
function custom_sitemap_generate_handler() {
    check_ajax_referer( 'custom_sitemap_nonce', 'nonce' );

    $config = isset( $_POST['config'] ) ? $_POST['config'] : array();
    $posts_data = isset( $_POST['posts'] ) ? $_POST['posts'] : array();

    // Sanitize config
    $sanitized_config = array(
        'site_url' => esc_url_raw( home_url() ),
        'post_types' => array_map( 'sanitize_text_field', $config['post_types'] ?? array() ),
        'default_priority' => floatval( $config['default_priority'] ?? 0.5 ),
        'default_changefreq' => sanitize_text_field( $config['default_changefreq'] ?? 'weekly' ),
    );

    // Sanitize posts data (basic sanitization, more robust needed for production)
    $sanitized_posts_data = array_map( function($post) {
        return array(
            'id' => intval($post['id']),
            'url' => esc_url_raw($post['url']),
            'modified_gmt' => sanitize_text_field($post['modified_gmt']),
            'post_type' => sanitize_text_field($post['post_type']),
            'priority' => isset($post['priority']) ? floatval($post['priority']) : null,
            'changefreq' => isset($post['changefreq']) ? sanitize_text_field($post['changefreq']) : null,
        );
    }, $posts_data);


    // Prepare data for Wasm module
    $config_json = json_encode( $sanitized_config );
    $posts_json = json_encode( $sanitized_posts_data );

    // This part requires the Wasm module to be accessible from the server-side
    // or a client-side call. For simplicity, we'll assume a client-side call
    // or a server-side Wasm runtime (e.g., Wasmtime, Wasmer).
    // For a typical WordPress plugin, the Wasm execution happens in the browser.
    // The PHP backend's role is to *fetch* the data needed by the Wasm module.
    // The actual Wasm execution and sitemap generation will be triggered from JS.

    // If you were to run Wasm server-side, you'd use a PHP extension or a separate process.
    // For this guide, we'll focus on the client-side execution triggered by the block.

    // The response here will be handled by the JS part of the block.
    // We'll return success to indicate the data is ready for JS processing.
    wp_send_json_success( array(
        'message' => 'Data prepared for Wasm generation.',
        'config_json' => $config_json,
        'posts_json' => $posts_json,
    ) );
}
add_action( 'wp_ajax_custom_sitemap_generate', 'custom_sitemap_generate_handler' );

// AJAX handler to save the generated sitemap XML
function custom_sitemap_save_sitemap_handler() {
    check_ajax_referer( 'custom_sitemap_nonce', 'nonce' );

    $sitemap_xml = isset( $_POST['sitemap_xml'] ) ? $_POST['sitemap_xml'] : '';

    if ( empty( $sitemap_xml ) ) {
        wp_send_json_error( array( 'message' => 'No sitemap XML provided.' ) );
        return;
    }

    // Sanitize XML content - this is crucial and complex.
    // For simplicity, we'll assume the Wasm output is trusted.
    // In a real-world scenario, you'd validate against an XML schema.

    $upload_dir = wp_upload_dir();
    $sitemap_dir = $upload_dir['basedir'] . '/custom-sitemaps/';
    if ( ! file_exists( $sitemap_dir ) ) {
        wp_mkdir_p( $sitemap_dir );
    }

    $file_path = $sitemap_dir . 'sitemap.xml';
    $result = file_put_contents( $file_path, $sitemap_xml );

    if ( $result === false ) {
        wp_send_json_error( array( 'message' => 'Failed to save sitemap file.' ) );
    } else {
        // Optionally, update a WordPress option to store the sitemap URL
        update_option( 'custom_sitemap_url', $upload_dir['baseurl'] . '/custom-sitemaps/sitemap.xml' );
        wp_send_json_success( array( 'message' => 'Sitemap saved successfully.', 'url' => $upload_dir['baseurl'] . '/custom-sitemaps/sitemap.xml' ) );
    }
}
add_action( 'wp_ajax_custom_sitemap_save_sitemap', 'custom_sitemap_save_sitemap_handler' );

B. Block Editor JavaScript (React)

This is the core of the Gutenberg block. It will handle user interface elements, fetch post data via AJAX, call the Wasm module, and display the results.

/**
 * WordPress dependencies
 */
const { registerBlockType } = wp.blocks;
const { useState, useEffect, useCallback } = wp.element;
const { InspectorControls, PanelColorSettings } = wp.blockEditor;
const { PanelBody, Button, SelectControl, TextControl, Spinner, Placeholder, Notice } = wp.components;
const { __ } = wp.i18n;

/**
 * Internal dependencies
 */
import './style.scss'; // For editor styles
import './editor.scss'; // For editor styles

// Assume wasm_sitemap_generator is globally available after loading the JS bindings
// from the PHP registration.
// Example: const { generate_sitemap } = window.wasm_sitemap_generator;

const Edit = ( { attributes, setAttributes } ) => {
    const [ isLoading, setIsLoading ] = useState( false );
    const [ error, setError ] = useState( null );
    const [ sitemapUrl, setSitemapUrl ] = useState( '' );
    const [ posts, setPosts ] = useState( [] );
    const [ config, setConfig ] = useState( {
        post_types: [ 'post', 'page' ],
        default_priority: 0.5,
        default_changefreq: 'weekly',
    } );
    const [ availablePostTypes, setAvailablePostTypes ] = useState( [] );
    const [ currentPage, setCurrentPage ] = useState( 1 );
    const [ maxPages, setMaxPages ] = useState( 1 );

    // Fetch available post types on mount
    useEffect( () => {
        wp.apiFetch( { path: '/wp/v2/types' } ).then( ( types ) => {
            const selectableTypes = Object.keys( types )
                .filter( type => types[ type ].viewable && types[ type ].public )
                .map( type => ( { label: types[ type ].name, value: type } ) );
            setAvailablePostTypes( selectableTypes );
        } );
    }, [] );

    // Load Wasm module and initialize
    useEffect( () => {
        const loadWasmModule = async () => {
            if ( typeof window.wasm_sitemap_generator === 'undefined' ) {
                setError( __( 'WebAssembly module not loaded. Please check plugin setup.', 'custom-sitemap' ) );
                return;
            }
            // The wasm_sitemap_generator_bg.js file should have exposed the generate_sitemap function.
            // We can test if it's available.
            if ( typeof window.wasm_sitemap_generator.generate_sitemap === 'undefined' ) {
                 setError( __( 'WebAssembly sitemap function not found.', 'custom-sitemap' ) );
            }
        };
        loadWasmModule();
    }, [] );

    const fetchPosts = useCallback( async ( page = 1 ) => {
        setIsLoading( true );
        setError( null );
        try {
            const response = await wp.apiFetch( {
                path: customSitemapBlock.ajax_url,
                method: 'POST',
                data: {
                    action: 'custom_sitemap_fetch_posts',
                    nonce: customSitemapBlock.nonce,
                    post_types: config.post_types,
                    paged: page,
                    posts_per_page: 100, // Fetch in batches
                },
            } );

            if ( response.success ) {
                setPosts( response.data.posts );
                setMaxPages( response.data.max_pages );
                setCurrentPage( page );
            } else {
                setError( response.data.message || __( 'Failed to fetch posts.', 'custom-sitemap' ) );
            }
        } catch ( e ) {
            setError( e.message || __( 'An error occurred while fetching posts.', 'custom-sitemap' ) );
        } finally {
            setIsLoading( false );
        }
    }, [ config.post_types ] );

    const handleGenerateSitemap = async () => {
        if ( typeof window.wasm_sitemap_generator.generate_sitemap === 'undefined' ) {
            setError( __( 'WebAssembly module is not ready.', 'custom-sitemap' ) );
            return;
        }

        setIsLoading( true );
        setError( null );

        // First, ensure we have the latest posts data
        await fetchPosts(1); // Fetch first page to ensure data is fresh

        if ( posts.length === 0 ) {
            setError( __( 'No posts found for the selected post types.', 'custom-sitemap' ) );
            setIsLoading( false );
            return;
        }

        try {
            const configJson = JSON.stringify( config );
            const postsJson = JSON.stringify( posts );

            // Call the Wasm function
            const resultJson = window.wasm_sitemap_generator.generate_sitemap( configJson, postsJson );

            if ( resultJson instanceof Error ) {
                 throw resultJson;
            }

            const sitemapData = JSON.parse( resultJson );

            // Format the sitemap XML from the Wasm output
            let sitemapXml = '<?xml version="1.0" encoding="UTF-8"?>\n';
            sitemapXml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"\n';
            sitemapXml += '        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"\n';
            sitemapXml += '        xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9\n';
            sitemapXml += '        http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd">\n';

            sitemapData.urls.forEach( urlEntry => {
                sitemapXml += `    <url>\n`;
                sitemapXml += `        <loc>${urlEntry.loc}</loc>\n`;
                sitemapXml += `        <lastmod>${urlEntry.lastmod}</lastmod>\n`;
                sitemapXml += `        <changefreq>${urlEntry.changefreq}</changefreq>\n`;
                sitemapXml += `        <priority>${urlEntry.priority.toFixed(1)}</priority>\n`;
                sitemapXml += `    </url>\n`;
            } );

            sitemapXml += '</urlset>';

            // Save the sitemap XML via AJAX
            const saveResponse = await wp.apiFetch( {
                path: customSitemapBlock.ajax_url,
                method: 'POST',
                data: {
                    action: 'custom_sitemap_save_sitemap',
                    nonce: customSitemapBlock.nonce,
                    sitemap_xml: sitemapXml,
                },
            } );

            if ( saveResponse.success ) {
                setSitemapUrl( saveResponse.data.url );
                setError( null ); // Clear previous errors
            } else {
                setError( saveResponse.data.message || __( 'Failed to save sitemap.', 'custom-sitemap' ) );
            }

        } catch ( e ) {
            console.error( "Wasm generation error:", e );
            setError( e.message || __( 'An error occurred during sitemap generation.', 'custom-sitemap' ) );
        } finally {
            setIsLoading( false );
        }
    };

    const handlePostTypeChange = ( value ) => {
        setConfig( { ...config, post_types: value } );
        setPosts([]); // Clear posts when post types change
        setSitemapUrl(''); // Clear saved URL
    };

    const handlePriorityChange = ( value ) => {
        setConfig( { ...config, default_priority: parseFloat( value ) || 0.5 } );
    };

    const handleChangefreqChange = ( value ) => {
        setConfig( { ...config, default_changefreq: value } );
    };

    const handleFetchClick = () => {
        fetchPosts(1);
    };

    const handleNextPage = () => {
        if ( currentPage < maxPages ) {
            fetchPosts( currentPage + 1 );
        }
    };

    const handlePrevPage = () => {
        if ( currentPage > 1 ) {
            fetchPosts( currentPage - 1 );
        }
    };

    if ( error ) {
        return (
            <Placeholder label={ __( 'Sitemap Generator', 'custom-sitemap' ) }>
                <Notice status="error" isDismissible={false}>{error}</Notice>
                <Button isPrimary onClick={handleFetchClick} disabled={isLoading}>{ __( 'Retry Fetch Posts', 'custom-sitemap' ) }</Button>
            </Placeholder>
        );
    }

    if ( isLoading ) {
        return (
            <Placeholder label={ __( 'Sitemap Generator', 'custom-sitemap' ) }>
                <Spinner />
                <p>{ __( 'Processing...', 'custom-sitemap' ) }</p>
            </Placeholder>
        );
    }

    return (
        <div className="custom-sitemap-block">
            <InspectorControls>
                <PanelBody title={ __( 'Sitemap Settings', 'custom-sitemap' ) } initialOpen={ true }>
                    <SelectControl
                        label={ __( 'Post Types', 'custom-sitemap' ) }
                        multiple
                        help={ __( 'Select which post types to include.', 'custom-sitemap' ) }
                        options={ availablePostTypes }
                        value={ config.post_types }
                        onChange={ handlePostTypeChange }
                    />
                    <TextControl
                        label={ __( 'Default Priority', 'custom-sitemap' ) }
                        type="number"
                        step="0.1"
                        min="0.0"
                        max="1.0"
                        value={ config.default_priority }
                        onChange={ handlePriorityChange }
                        help={ __( 'Default priority for URLs (0.0 to 1.0).', 'custom-sitemap' ) }
                    />
                    <SelectControl
                        label={ __( 'Default Change Frequency', 'custom-sitemap' ) }
                        value={ config.default_changefreq }
                        options={ [
                            { label: __( 'Always', 'custom-sitemap' ), value: 'always' },
                            { label: __( 'Hourly', 'custom-sitemap' ), value: 'hourly' },
                            { label: __( 'Daily', 'custom-sitemap' ), value: 'daily' },
                            { label: __( 'Weekly', 'custom-sitemap' ), value: 'weekly' },
                            { label: __( 'Monthly', 'custom-sitemap' ), value: 'monthly' },
                            { label: __( 'Yearly', 'custom-sitemap' ), value: 'yearly' },
                            { label: __( 'Never', 'custom-sitemap' ), value: 'never' },
                        ] }
                        onChange={ handleChangefreqChange }
                        help={ __( 'Default change frequency for URLs.', 'custom-sitemap' ) }
                    />
                    <Button isPrimary onClick={ handleFetchClick } disabled={ isLoading || availablePostTypes.length === 0 }>
                        { __( 'Fetch Posts', 'custom-sitemap' ) }
                    </Button>
                </PanelBody>
            </InspectorControls>

            <Placeholder label={ __( 'XML Sitemap Generator', 'custom-sitemap' ) }>
                { availablePostTypes.length === 0 && !error && <Spinner /> }
                { availablePostTypes.length > 0 && (
                    <>
                        <p>{ __( 'Configure your sitemap settings in the sidebar.', 'custom-sitemap' ) }</p>
                        { posts.length > 0 && (
                            <div>
                                <p>{ __( 'Found', 'custom-sitemap' ) } { posts.length } { __( 'posts for the selected post types.', 'custom-sitemap' ) }</p>
                                <div className="pagination">
                                    <Button onClick={ handlePrevPage } disabled={ currentPage === 1 }>{ __( 'Previous', 'custom-sitemap' ) }</Button>
                                    <span>{ currentPage } / { maxPages }</span>
                                    <Button onClick={ handleNextPage } disabled={ currentPage === maxPages }>{ __( 'Next', 'custom-sitemap' ) }</Button>
                                </div>
                            </div>
                        ) }
                        <Button isPrimary onClick={ handleGenerateSitemap } disabled={ isLoading || posts.length === 0 }>
                            { __( 'Generate & Save Sitemap', 'custom-sitemap' ) }
                        </Button>
                        { sitemapUrl && (
                            <p>
                                { __( 'Sitemap saved at:', 'custom-sitemap' ) }&nbsp;
                                <a href={ sitemapUrl } target="_blank" rel="noopener noreferrer">{ sitemapUrl }</a>
                            </p>
                        ) }
                    </>
                ) }
            </Placeholder>
        </div>
    );
};

registerBlockType( 'custom-sitemap/generator', {
    title: __( 'XML Sitemap Generator', 'custom-sitemap' ),
    icon: 'admin-site-alt3', // Choose an appropriate icon
    category: 'widgets', // Or 'design', 'plugins', etc.
    edit: Edit,
    save: () => null, // This block is purely for the editor/admin interface
} );

C. Build Process

You’ll need Node.js and npm/yarn. Use `@wordpress/scripts` to compile your JavaScript and SCSS.

# Navigate to your plugin directory
cd /path/to/your/wordpress/wp-content/plugins/custom-sitemap-block/

# Install dependencies
npm install

# Build the block assets
npm run build

This will create the build/index.js and build/index.asset.php files, which are referenced in the PHP registration. Ensure your Wasm output directory (e.g., public/wasm) is correctly placed relative to your main plugin file.

III. Deployment and Usage

A. Plugin Structure

Your plugin directory should look something like this:

custom-sitemap-block/
├── build/
│   ├── index.asset.php
│   └── index.js
├── public/
│   └── wasm/
│       ├── wasm_sitemap_generator_bg.js
│       └── wasm_sitemap_generator_bg.wasm
├── src/
│   ├── editor.scss
│   ├── index.js
│   └── style.scss
├── custom-sitemap-block.php
└── package.json
└── Cargo.toml (if you keep Rust source within plugin)
└── ... (other Rust source files if kept within plugin)

Note: It’s often better practice to build the Wasm module separately and then copy the generated .wasm and _bg.js files into your plugin’s public/wasm/ directory. If you keep the Rust source within the plugin, ensure your wasm-pack build command targets the correct output path.

B. Activating and Using the Block

1. Upload the plugin folder to your WordPress wp-content/plugins/ directory.
2. Activate the “Custom Sitemap Generator Block” plugin from the WordPress admin.
3. Edit any page or post, or create a new one.
4. Click the ‘+’ icon to add a new block and search for “XML Sitemap Generator”.
5. Select the block to add it to the editor.
6. Use the block’s sidebar (Inspector Controls) to configure post types, default priority, and change frequency.
7. Click “Fetch Posts” to retrieve the latest posts based on your configuration.
8. Click “Generate & Save Sitemap” to trigger the Wasm generation and save the resulting sitemap.xml file to your uploads directory (wp-content/uploads/custom-

Primary Sidebar

A little about the Author

Having 12+ Years of Experience in Software Development, Vinay is a principal software architect, senior systems engineer, and elite technical consultant. He specializes in bespoke PHP/WordPress development, high-performance Magento 2 & Shopify architectures, custom plugin/theme development from scratch, and legacy code modernization (including VB6, VB.NET, PyQt, and Crystal Reports). Known for solving complex database bottlenecks, speed optimization (Core Web Vitals), and advanced security code auditing, Vinay engineers production-ready systems designed to scale under heavy concurrent load conditions.



Chat on WhatsApp

Recent Posts

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins
  • How to implement native Redis caching layers for high-volume custom taxonomy queries in Carbon Fields custom wrappers
  • Building secure B2B pricing grids with custom REST API Controllers endpoints and role overrides

Categories

  • apache (1)
  • Business & Monetization (390)
  • Centos (4)
  • Comparisons & Decision Making (55)
  • Debian (2)
  • Debugging & Troubleshooting (658)
  • Desktop Applications (14)
  • DevOps (7)
  • DevOps & Cloud Scaling (962)
  • Django (1)
  • Laravel (4)
  • Migration & Architecture (192)
  • Mobile Applications (24)
  • MySQL (1)
  • Performance & Optimization (872)
  • PHP (5)
  • PHP Development (48)
  • Plugins & Themes (244)
  • Programming Languages (9)
  • Python (20)
  • Ruby on Rails (1)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Server (23)
  • Ubuntu (9)
  • VB6 & VB.NET (8)
  • Web Applications & Frontend (19)
  • Web Assembly (Wasm) (2)
  • WordPress (22)
  • WordPress Plugin Development (182)
  • WordPress Plugin Development (197)
  • WordPress Plugin Development (330)
  • WordPress Theme Development (357)

Recent Posts

  • Reducing database query bloat in Sage Roots modern environments layouts using custom lazy loaders
  • Performance Optimization: Tuning PHP-FPM and opcache pools for high-concurrency Firebase Realtime DB handlers
  • Reducing Largest Contentful Paint (LCP) by optimizing custom script enqueuing structures in legacy plugins

Top Categories

  • DevOps & Cloud Scaling (962)
  • Performance & Optimization (872)
  • Debugging & Troubleshooting (658)
  • Security & Compliance (639)
  • SEO & Growth (492)
  • Business & Monetization (390)

Our Products

  • ERP & LMS Systems (4)
  • Directories & Marketplaces (4)
  • Healthcare Portals (3)
  • Point of Sale (POS) (2)
  • E-Commerce Engines (2)

Our Services

  • E-Commerce Development (10)
  • WordPress Development (8)
  • Python & Desktop GUI (7)
  • General Consulting (7)
  • Legacy Modernization (5)
  • Mobile App Development (4)

Copyright © 2026 · Vinay Vengala