DEV Community

Cover image for Building a Dynamic XML Sitemap That Scales Beyond Plugin Limits
Дарья
Дарья

Posted on

Building a Dynamic XML Sitemap That Scales Beyond Plugin Limits

When your WordPress site hits 50+ posts, your SEO plugin's sitemap starts dropping pages. I found out the hard way — my site had 75 posts, but only 49 showed up in post-sitemap.xml.

The Plugin Ceiling

Rank Math Free generates sitemaps automatically. But it has an undocumented limit on entries per sitemap file. Post #50 pushes post #1 out. No warning, no error — just silent removal.

I only noticed because Google Search Console showed "Discovered - currently not indexed" for pages that used to rank. They'd been quietly removed from the sitemap weeks earlier.

The PHP Solution

Instead of fighting the plugin, I built a supplementary sitemap that catches everything the plugin misses:

<?php
header('Content-Type: application/xml; charset=utf-8');
require_once(__DIR__ . '/wp-load.php');

$posts = get_posts([
    'post_type' => 'post',
    'post_status' => 'publish',
    'posts_per_page' => -1
]);

// Fetch existing plugin sitemap URLs
$xml = @file_get_contents(home_url('/post-sitemap.xml'));
preg_match_all('/<loc>([^<]+)<\/loc>/', $xml, $matches);
$existing = array_flip($matches[1] ?? []);

echo '<?xml version="1.0" encoding="UTF-8"?>';
echo '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';

foreach ($posts as $post) {
    $url = get_permalink($post);
    if (!isset($existing[$url])) {
        $mod = get_the_modified_date('Y-m-d', $post);
        echo "<url><loc>{$url}</loc>";
        echo "<lastmod>{$mod}</lastmod></url>";
    }
}
echo '</urlset>';
Enter fullscreen mode Exit fullscreen mode

Key Design Decisions

Why not just increase the plugin limit? Rank Math Free doesn't expose that setting. The Pro version does, but I didn't want to pay for one config option.

Why check against the existing sitemap? To avoid duplicates. Google handles duplicate URLs gracefully, but clean sitemaps build more crawler trust. The array_flip trick makes the lookup O(1) instead of searching an array on every iteration.

Why posts_per_page => -1? Because we need every published post, not just the first 10. Yes, this loads all posts into memory. For 75 posts it's fine. For 10,000 you'd want pagination.

Deployment

  1. Drop the PHP file in your WordPress root (alongside wp-config.php)
  2. Add it to robots.txt: Sitemap: https://yoursite.com/ww-sitemap.php
  3. Submit in Google Search Console alongside your main sitemap
  4. It regenerates dynamically on every request — no cron needed

Results After 2 Weeks

  • Before: 49 URLs in sitemap, 26 posts invisible to Google
  • After: All 75 posts discoverable
  • Bing indexed the missing pages within 48 hours via IndexNow
  • Google... is still thinking about it (6 weeks and counting)

The full story of how this fit into a larger indexation recovery effort is on my site: watchwalls.pro/apple-watch-wallpaper-guide


Has anyone else hit plugin sitemap limits? Curious what solutions you found.

Top comments (0)