← Back to Course Index

Phase 4 — Track A: WordPress Technical SEO (In Depth)

WordPress powers over 40% of the web. That means most technical SEO work you encounter professionally will touch WordPress in some form. The goal of this track is to move you beyond plugin dashboards and into the engine room — understanding how WordPress actually renders, where it breaks, and how to fix it at the code level when plugins cannot reach.


1. The WordPress Rendering Model

Before you can audit or fix a WordPress site, you need to understand how a request becomes a rendered HTML page.

1.1 PHP, The Loop, and the Template Hierarchy

WordPress is a server-side PHP application. When a browser (or Googlebot) requests a URL, the following sequence occurs:

This means WordPress, by default, produces server-rendered HTML — crawlers receive real content in the initial response. However, themes, page builders, and plugins can layer JavaScript on top of that HTML, introducing all the rendering risks covered in Phase 0, Module 0.3.

1.2 The Template Hierarchy in Practice

WordPress uses a specific lookup order to find the right template. A simplified version:

Understanding this hierarchy is essential because custom SEO logic (custom meta, custom JSON-LD) must be attached to the right template or hooked into the right WordPress action.

1.3 Themes and Page Builders: Performance and Markup Cost

A raw WordPress theme can produce clean, semantic HTML quickly. A page builder (Elementor, Divi, WPBakery) wraps every element in multiple <div> layers, injects proprietary CSS and JS on every page regardless of whether those assets are needed, and frequently generates markup that breaks heading hierarchy and semantic structure.

The SEO costs:


2. The SEO Plugin Landscape

2.1 What SEO Plugins Control

The major plugins — Yoast SEO, Rank Math, and SEOPress — manage a consistent set of SEO features:

2.2 Plugin Limits — When to Stop Relying on a Plugin

SEO plugins are designed for general-purpose use. You must move to custom code when:

At that point, you write code. The next section shows you how.


3. Custom Code for SEO — Hooks, Filters, and Functions

3.1 Child Themes and functions.php

Never modify a parent theme directly — updates will overwrite your changes. Always create a child theme. Your customizations live in the child theme's functions.php, which WordPress loads after the parent theme.

<?php
// wp-content/themes/your-child-theme/functions.php

// Enqueue parent theme styles
add_action( 'wp_enqueue_scripts', function() {
    wp_enqueue_style(
        'parent-style',
        get_template_directory_uri() . '/style.css'
    );
});

3.2 WordPress Hooks: Actions and Filters

WordPress is event-driven. Almost everything that happens during a page request fires an action (a named event you can attach to) or passes data through a filter (a named pipeline you can intercept and modify).

Key SEO-relevant hooks:

3.3 Injecting a Custom Canonical Tag Programmatically

This example outputs a canonical tag based on custom logic — useful when your SEO plugin's canonical is wrong for a specific post type.

<?php
add_action( 'wp_head', 'custom_canonical_tag', 1 );

function custom_canonical_tag() {
    // Only run on singular product pages
    if ( ! is_singular( 'product' ) ) {
        return;
    }

    $post_id  = get_the_ID();
    // Retrieve a custom field that stores the canonical override
    $override = get_post_meta( $post_id, '_canonical_override', true );

    if ( $override ) {
        $canonical = esc_url( $override );
    } else {
        $canonical = esc_url( get_permalink( $post_id ) );
    }

    echo '<link rel="canonical" href="' . $canonical . '" />' . "\n";
}

Important: If you are also using an SEO plugin, disable its canonical output for this post type to avoid duplicate, potentially conflicting canonical tags. Most plugins provide a filter for this.

3.4 Injecting Custom JSON-LD

Below is an example that outputs a Product JSON-LD block on WooCommerce product pages, pulling data directly from the database. This produces richer, more accurate schema than most plugin defaults.

<?php
add_action( 'wp_head', 'custom_product_jsonld', 5 );

function custom_product_jsonld() {
    if ( ! is_singular( 'product' ) ) {
        return;
    }

    global $post;
    $product = wc_get_product( $post->ID );

    if ( ! $product ) {
        return;
    }

    $name        = esc_js( $product->get_name() );
    $description = esc_js( wp_strip_all_tags( $product->get_description() ) );
    $sku         = esc_js( $product->get_sku() );
    $price       = $product->get_price();
    $currency    = get_woocommerce_currency();
    $image_id    = $product->get_image_id();
    $image_url   = $image_id ? esc_url( wp_get_attachment_url( $image_id ) ) : '';
    $url         = esc_url( get_permalink() );

    // Availability
    $availability = $product->is_in_stock()
        ? 'https://schema.org/InStock'
        : 'https://schema.org/OutOfStock';

    $schema = [
        '@context' => 'https://schema.org',
        '@type'    => 'Product',
        'name'     => $product->get_name(),
        'url'      => get_permalink(),
        'sku'      => $product->get_sku(),
        'description' => wp_strip_all_tags( $product->get_description() ),
        'image'    => $image_url,
        'offers'   => [
            '@type'         => 'Offer',
            'price'         => $price,
            'priceCurrency' => $currency,
            'availability'  => $availability,
            'url'           => get_permalink(),
        ],
    ];

    echo '<script type="application/ld+json">'
        . wp_json_encode( $schema, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT )
        . '</script>' . "\n";
}

3.5 Modifying robots.txt via a Filter

WordPress generates a virtual robots.txt at the root of the site. You can modify it without editing a physical file:

<?php
add_filter( 'robots_txt', 'custom_robots_txt_rules', 10, 2 );

function custom_robots_txt_rules( $output, $public ) {
    // Add a Crawl-delay directive for all bots
    $output .= "\n# Custom rules\n";
    $output .= "Crawl-delay: 2\n";
    // Block a specific path
    $output .= "Disallow: /wp-json/\n";
    return $output;
}

Warning: Never disallow /wp-content/themes/ or /wp-content/plugins/. Blocking these resources prevents Googlebot from rendering your pages correctly, causing all the rendering failures described in Phase 0, Module 0.3f.


4. WordPress Performance Stack

Performance in WordPress is a layered problem. Each layer addresses a different bottleneck. You must understand every layer and how they interact — installing a caching plugin without understanding what it does is how you create bugs.

4.1 Page Caching

WordPress generates pages dynamically via PHP + MySQL on every request. For most pages, this content does not change between requests. Page caching stores the generated HTML output and serves it directly, bypassing PHP and the database entirely.

Key concerns: ensure logged-in users, cart pages (WooCommerce), and search result pages are excluded from cache. Cached pages with user-specific or dynamic content produce incorrect results.

4.2 Object Caching (Redis / Memcached)

WordPress makes database queries on every request even when a page cache exists (because not every request hits the page cache — e.g., admin users, logged-in users, dynamic fragments). Object caching stores the results of expensive database queries in memory.

4.3 OPcache

PHP compiles your PHP files to bytecode on every request by default. OPcache stores that compiled bytecode in memory, eliminating the recompilation step. This is a server-level PHP configuration — a significant TTFB improvement at zero application cost. Verify it is enabled on your hosting environment.

4.4 Database Optimization and Query Performance

4.5 Asset Optimization

4.6 CDN Integration

A CDN (Content Delivery Network) serves static assets (images, CSS, JS) from edge nodes geographically close to the user, reducing latency. It also offloads bandwidth from your origin server. Cloudflare is the most common choice; it also provides additional performance features (HTTP/2, HTTP/3, image optimization, edge caching).

Ensure your CDN is configured to:


5. Classic WordPress Technical SEO Problems

These are the issues you will find on almost every established WordPress site. Know each one, its cause, and its remedy.

5.1 Attachment and Media Pages

Problem: WordPress creates a public URL for every uploaded media file. These pages contain almost no content — usually just the image and its filename — yet they consume crawl budget and frequently get indexed, creating thousands of thin-content pages.

Fix: Redirect all attachment URLs to their parent post, or to the attachment file itself. Most SEO plugins have a one-click option to enable this. The underlying mechanism is a redirect from the attachment page URL to the attachment file URL (or parent post), so there is no indexed thin-content page.

5.2 Tag, Category, Author, and Date Archive Duplication

Problem: WordPress generates archive pages for every category, every tag, every author, and every date combination. A post tagged with 10 tags creates 10 additional archive pages containing that post's excerpt, often duplicating content that appears on the main category page.

Diagnostic approach:

Fixes:

5.3 Faceted Navigation and Parameter URL Bloat

Problem: E-commerce sites built on WooCommerce commonly use filtering plugins (e.g., WooCommerce Product Filters, FacetWP) that generate URL parameters for every filter combination (?color=red&size=large&sort=price). This can generate millions of unique URLs from a catalog of hundreds of products, overwhelming crawl budget and creating massive thin-content problems.

Fixes (in order of preference):

5.4 Default Sitemap Noise

WordPress's built-in sitemap (introduced in WordPress 5.5) and many SEO plugin sitemaps include URLs that should not be in a sitemap: attachment pages, author archives, date archives, tag archives, noindexed pages, low-value utility pages.

Audit your sitemap and remove any URL that:

Use the wp_sitemaps_posts_query_args, wp_sitemaps_taxonomies, and wp_sitemaps_add_provider filters to control what WordPress's core sitemap includes. SEO plugins expose their own filters and admin toggles.

5.5 Multiple H1 Tags from Themes and Page Builders

Problem: Many themes wrap the site name or tagline in an <h1> on every page. The post title also outputs as an <h1>. Result: every page has two <h1> tags, and neither correctly represents the page's primary topic.

Fix: In the child theme, override the header template to change the site title to a <p> or <span> on non-homepage templates, or to an <h1> only on the homepage (where it is the primary heading). Use browser DevTools to verify the heading outline of every template type.

5.6 Render-Blocking Builder CSS and JS

Audit with Chrome DevTools Coverage tab and the Network tab waterfall. For every render-blocking resource, determine:


6. Headless WordPress

WordPress can function purely as a content management backend with a completely decoupled frontend. This architecture is increasingly common in enterprise and performance-critical deployments.

6.1 WordPress as a Headless CMS

In a headless setup:

6.2 SEO Responsibilities Shift to the Front End

This is the critical architectural implication. When WordPress is headless, it no longer controls what gets rendered in the browser. SEO responsibilities move to the front-end framework:

This is the direct bridge to Track B (Payload / Headless). The patterns you implement in a Payload + Next.js project are architecturally identical to what you would build with WordPress + Next.js.

6.3 WPGraphQL

The REST API returns data in a fixed schema; every request fetches a full object even if you need only a few fields. WPGraphQL allows the front end to request exactly the fields it needs, reducing over-fetching. For SEO-specific data, a query fetching meta title, description, canonical, and JSON-LD fields becomes tightly scoped:

query GetPostSEO($slug: String!) {
  postBy(slug: $slug) {
    title
    excerpt
    slug
    featuredImage {
      node {
        sourceUrl
        altText
      }
    }
    seo {
      metaDesc
      canonical
      opengraphTitle
      opengraphDescription
    }
  }
}

The seo fields in the example above are provided by the Yoast SEO WPGraphQL integration (wp-graphql-yoast-seo plugin). Rank Math has an equivalent.


7. WordPress Migrations

A WordPress migration that loses rankings is a common and avoidable failure. Migrations include domain changes, hosting moves, URL structure changes, and platform changes from or to WordPress.

7.1 Pre-Migration Steps

7.2 Redirect Mapping

Every URL that existed on the old site must either:

Do not redirect all old URLs to the homepage. This is a soft 404 and Google ignores the redirect, treating the original URL as a 404 anyway.

Implement redirects at the server level (Nginx, Apache) or CDN level — not via a WordPress plugin, which adds PHP execution overhead to every redirect request.

7.3 Launch and Post-Launch QA Checklist


8. Hands-On Exercises

Exercise 1 — Custom Child Theme with Hooks

Create a child theme for any WordPress installation. In functions.php:

Exercise 2 — Full Technical Audit

Choose a live WordPress site (your own, a client's, or a publicly accessible one with permission). Produce a written audit report covering:


9. Module Milestone

You have completed Track A when you can do all of the following without looking things up:

When all of the above are second nature, move to Track B — Payload (Headless / Code-First) Technical SEO.