Skip to main content

I Let an AI Agent Ship Live WordPress Pages and a www Redirect

How an AI coding agent shipped self-updating WordPress pages from a public API, fixed virtual-route SEO, pruned content with rollback, and closed www duplicates in nginx.

My fantasy football side project had four boring problems that kept hurting SEO and the weekly pages: stale timing copy in old posts, theme-only tool routes inheriting the homepage title, a www host that still answered with 200, and weekly pages that were either redirected away or stuck on old theme markup.

🎯 Not sure if this will run on your hardware?Use our free Local LLM Hardware Checker — pick your GPU and RAM, see which models will run with real tokens/sec estimates.
Check my hardware →

This time I did not open the editor myself. I handed the job to an AI coding agent working from a sandboxed Linux box with a shell, curl, and the same WordPress admin paths I would use. My part was the rules: back up before you deploy, dry-run before you apply, keep a rollback, and prove every change with a request I can re-run. The result is a small set of self-hosted WordPress pieces that keep updating themselves. There’s no theme fork and no made-up Lighthouse scores.

The stack (generic on purpose)

  • Self-hosted WordPress behind nginx, managed through a hosted nginx control panel with a page cache in front
  • An SEO plugin for titles, schema, sitemaps, and 301s
  • One MU-plugin for virtual-route titles and descriptions, plus adding the tools and news sitemaps to the sitemap index
  • One live-pages plugin that server-renders the weekly picks, team news, and a stats table from a public sports API
  • One operator plugin with Dry Run → Apply → Rollback for a content prune and merge (deactivated after the ship)
  • One server-level nginx rule that sends every www path to the apex with a single 301
  • The AI coding agent, which wrote the plugins, ran the checks, and wrote up what changed after every step
Advertisement

How I split the work with the agent

The agent is fast and confident, which is exactly why I gave it a fixed loop:

  1. Crawl first. It fetches the live URLs and sorts problems into three buckets: content (fixable over REST or in the editor), virtual routes (the theme renders them and they aren’t WordPress pages), and theme-injected markup.
  2. Build locally. Plugins are written and linted (php -l) on the box before anything touches production.
  3. Back up, then deploy. The previous plugin version is kept so a rollback is one upload.
  4. Verify with curl, not screenshots. Status codes, Location headers, titles, and the sitemap index are checked against what I asked for.
  5. Ask before anything irreversible. Deleting or merging content goes through a dry run I can read first.

That loop comes from my 7 rules for letting an AI agent maintain your homelab, applied to a production WordPress site this time instead of a home server.

What the agent actually shipped

1. Live pages plugin (shortcodes and cache)

Shortcodes live on fixed slugs instead of a new URL every week:

  • A weekly picks page with a shortcode that ranks options for the current round
  • A team news page, either as a full shortcode page or in an “extras” mode that drops into the existing theme template
  • A stats table embedded in an evergreen guide

The plugin fetches the public API server-side and keeps a short transient cache. If the API blips it falls back to the last good copy, and a lock stops parallel requests from stampeding the API. The SEO title and description pick up the current round automatically, and the FAQPage JSON-LD is printed from the same data as the visible FAQ so the two can’t drift. The core fetch pattern looks like this:

function lp_get_api( $url, $key, $ttl = 300 ) {
    $data = get_transient( $key );
    if ( false !== $data ) {
        return $data;
    }
    // Someone else is refreshing: serve the last good copy.
    if ( get_transient( $key . '_lock' ) ) {
        return get_option( $key . '_last_good', array() );
    }
    set_transient( $key . '_lock', 1, 30 );
    $res = wp_remote_get( $url, array( 'timeout' => 10 ) );
    delete_transient( $key . '_lock' );

    if ( is_wp_error( $res ) || 200 !== wp_remote_retrieve_response_code( $res ) ) {
        return get_option( $key . '_last_good', array() );
    }
    $data = json_decode( wp_remote_retrieve_body( $res ), true );
    if ( ! is_array( $data ) ) {
        return get_option( $key . '_last_good', array() );
    }
    set_transient( $key, $data, $ttl );
    update_option( $key . '_last_good', $data, false );
    return $data;
}

// Example: lp_get_api( 'https://api.example.com/v1/bootstrap/', 'lp_bootstrap' );

2. MU-plugin for virtual routes and the sitemap index

A handful of tool URLs are virtual routes that the theme renders. They aren’t WordPress pages, so they were inheriting the homepage meta. The MU-plugin gives each one its own title and description, and adds the existing tools and news sitemaps to the sitemap index next to posts, pages, and authors. If your SEO plugin is Rank Math, the hooks look roughly like this:

$routes = array(
    '/tools/compare/' => array( 'Compare Tool | Example', 'Compare two options side by side.' ),
    '/tools/planner/' => array( 'Planner | Example', 'Plan the next few rounds.' ),
);
$path = trailingslashit( wp_parse_url( $_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH ) );

add_filter( 'rank_math/frontend/title', function ( $t ) use ( $routes, $path ) {
    return isset( $routes[ $path ] ) ? $routes[ $path ][0] : $t;
} );
add_filter( 'rank_math/frontend/description', function ( $d ) use ( $routes, $path ) {
    return isset( $routes[ $path ] ) ? $routes[ $path ][1] : $d;
} );
add_filter( 'rank_math/sitemap/index', function ( $xml ) {
    foreach ( array( 'tools-sitemap.xml', 'news-sitemap.xml' ) as $map ) {
        $xml .= '<sitemap><loc>' . esc_url( home_url( "/$map" ) ) . '</loc></sitemap>';
    }
    return $xml;
}, 11 );

Other SEO plugins have equivalent filters. The point is that the fix sits in an MU-plugin and the theme stays untouched.

3. Prune and merge with Dry Run → Apply → Rollback

Evergreen topic clusters each got one keeper post, with 301s from the duplicates. Last season’s weekly posts now point at the live pages or tools. The agent built this as a one-shot operator plugin:

  • Dry Run lists every post, meta, and redirect write it would make. I read that list before anything changes.
  • Apply works in chunks under a time budget, so a big pass can’t hit the PHP timeout halfway through.
  • Rollback reverses the file, post, meta, and redirect writes from its own log.

Once everything was verified the operator was deactivated. The redirects and keepers stay, and the one-shot applicator doesn’t keep running.

4. Close the www duplicates at the server, not in WordPress

WordPress’s canonical redirect already sent normal pages from www to the apex. The theme’s virtual routes and the sitemaps still answered 200 on www, because they never reach redirect_canonical. The fix is one server-level rule:

if ($host = "www.example.com") {
    return 301 https://example.com$request_uri;
}

On a managed nginx panel this usually goes in a custom config include that loads before the main location block. Save it, let the panel run nginx -t and reload, then purge the page cache. Check that the TLS certificate covers both the apex and www before you rely on the redirect.

What broke (and how the agent caught it)

  • The www host once answered with the wrong certificate. After that was fixed, normal pages redirected but the virtual routes and sitemaps still served on www. The agent caught it by curling every route on both hosts, not just the homepage.
  • The new picks page URL was 301ing to an old slug (x-redirect-by: WordPress), so an old slug redirect was sitting in front of the new page. The Location header gave it away immediately.
  • Timing copy in old posts and injected SEO blocks still described a schedule the game no longer uses.
  • The homepage and a few tool pages had a second <h1> from a content injector. Demoting those to <h2> left one real H1.
  • The sitemap index listed only posts, pages, and authors. The tools and news sitemaps already existed but were missing from the index.

Step by step (operator order)

  1. Back up the site (panel backup or your usual plugin).
  2. Drop the MU-plugin into wp-content/mu-plugins/, then re-save your SEO plugin’s sitemap settings to flush the index cache.
  3. Install and activate the live-pages plugin. Free up the target slug if an old redirect still claims it. Publish the shortcode pages.
  4. Run the operator’s Dry Run, read the list, Apply, and draft the redirected posts so they drop out of the post sitemap.
  5. Add the www → apex nginx rule and purge the page cache.
  6. Re-run the checks below.
  7. Deactivate the one-shot operator plugin, and delete it once you’re happy.

The checks the agent re-runs after every change:

curl -sI https://www.example.com/tools/compare/ | grep -iE '^(HTTP|location)'
curl -s https://example.com/sitemap_index.xml | grep -oE '(tools|news)-sitemap\.xml'
curl -s https://example.com/picks/ | grep -o '<title>[^<]*'

Ship checklist

  1. Have the agent sort content fixes apart from virtual-route and theme-injected problems before it writes any code.
  2. Prefer a shortcode plugin plus an MU-plugin over editing the theme in place.
  3. Give every bulk pass a Dry Run and a Rollback before you trust Apply on production.
  4. Don’t rely on WordPress redirect_canonical for theme routes. Put www → apex in nginx.
  5. Purge the page cache after every activation, deactivation, or nginx change.
  6. Make the agent show you the request that proves each fix, and never invent traffic or score claims in the ship notes.
Advertisement

FAQ

Why not one giant plugin for SEO, live pages, and redirects?

I wanted each layer to be something I could switch off on its own. The live-pages plugin stays on and so does the MU-plugin. The Apply/Rollback operator is a one-shot and shouldn’t keep running. Small plugins are also easier to review when an agent wrote them.

Did the agent have free rein on production?

No. It worked through the same admin and REST paths I would use, it backed up before each deploy, and every bulk change went through a dry run first. Anything that deletes, merges, or publishes waits for a human.

Do I need a local GPU for this?

No. This ship is a coding agent, self-hosted WordPress, nginx, and a public API. If you want the local-model side, my local LLM homelab stack post covers what I keep running.

Closest related Builders notes?

I Built an Admin-Only WordPress CRM With Cursor covers another AI-assisted admin plugin, and I Let an AI Agent Patch a Theme With a Tiny Reversible Plugin covers the “plugin over theme edit” rule.

Written by MustafaEngineer running a 24/7 homelab since 2022. Every guide here is built and tested on my own hardware. No paid placements.

Keep reading