Claude
Skills
Sign in
Back

wp-performance-review

Included with Lifetime
$97 forever

WordPress performance code review and optimization analysis. Use when reviewing WordPress PHP code for performance issues, auditing themes/plugins for scalability, optimizing WP_Query, analyzing caching strategies, checking code before launch, or detecting anti-patterns, or when user mentions "performance review", "optimization audit", "slow WordPress", "slow queries", "high-traffic", "scale WordPress", "code review", "timeout", "500 error", "out of memory", or "site won't load". Detects anti-patterns in database queries, hooks, object caching, AJAX, and template loading.

Web Dev

What this skill does


# WordPress Performance Review Skill

## Overview

Systematic performance code review for WordPress themes, plugins, and custom code. **Core principle:** Scan critical issues first (OOM, unbounded queries, cache bypass), then warnings, then optimizations. Report with line numbers and severity levels.

## When to Use

**Use when:**
- Reviewing PR/code for WordPress theme or plugin
- User reports slow page loads, timeouts, or 500 errors
- Auditing before high-traffic event (launch, sale, viral moment)
- Optimizing WP_Query or database operations
- Investigating memory exhaustion or DB locks

**Don't use for:**
- Security-only audits (use wp-security-review when available)
- Gutenberg block development patterns (use wp-gutenberg-blocks when available)
- General PHP code review not specific to WordPress

## Code Review Workflow

1. **Identify file type** and apply relevant checks below
2. **Scan for critical patterns first** (OOM, unbounded queries, cache bypass)
3. **Check warnings** (inefficient but not catastrophic)
4. **Note optimizations** (nice-to-have improvements)
5. **Report with line numbers** using output format below

## File-Type Specific Checks

### Plugin/Theme PHP Files (`functions.php`, `plugin.php`, `*.php`)
Scan for:
- `query_posts()` → CRITICAL: Never use - breaks main query
- `posts_per_page.*-1` or `numberposts.*-1` → CRITICAL: Unbounded query
- `session_start()` → CRITICAL: Bypasses page cache
- `add_action.*init.*` or `add_action.*wp_loaded` → Check if expensive code runs every request
- `update_option` or `add_option` in non-admin context → WARNING: DB writes on page load
- `wp_remote_get` or `wp_remote_post` without caching → WARNING: Blocking HTTP

### WP_Query / Database Code
Scan for:
- Missing `posts_per_page` argument → WARNING: Defaults to blog setting
- `'meta_query'` with `'value'` comparisons → WARNING: Unindexed column scan
- `post__not_in` with large arrays → WARNING: Slow exclusion
- `LIKE '%term%'` (leading wildcard) → WARNING: Full table scan
- Missing `no_found_rows => true` when not paginating → INFO: Unnecessary count

### AJAX Handlers (`wp_ajax_*`, REST endpoints)
Scan for:
- `admin-ajax.php` usage → INFO: Consider REST API instead
- POST method for read operations → WARNING: Bypasses cache
- `setInterval` or polling patterns → CRITICAL: Self-DDoS risk
- Missing nonce verification → Security issue (not performance, but flag it)

### Template Files (`*.php` in theme)
Scan for:
- `get_template_part` in loops → WARNING: Consider caching output
- Database queries inside loops (N+1) → CRITICAL: Query multiplication
- `wp_remote_get` in templates → WARNING: Blocks rendering

### JavaScript Files
Scan for:
- `$.post(` for read operations → WARNING: Use GET for cacheability
- `setInterval.*fetch\|ajax` → CRITICAL: Polling pattern
- `import _ from 'lodash'` → WARNING: Full library import bloats bundle
- Inline `<script>` making AJAX calls on load → Check necessity

### Block Editor / Gutenberg Files (`block.json`, `*.js` in blocks/)
Scan for:
- Many `registerBlockStyle()` calls → WARNING: Each creates preview iframe
- `wp_kses_post($content)` in render callbacks → WARNING: Breaks InnerBlocks
- Static blocks without `render_callback` → INFO: Consider dynamic for maintainability

### Asset Registration (`functions.php`, `*.php`)
Scan for:
- `wp_enqueue_script` without version → INFO: Cache busting issues
- `wp_enqueue_script` without `defer`/`async` strategy → INFO: Blocks rendering
- Missing `THEME_VERSION` constant → INFO: Version management
- `wp_enqueue_script` without conditional check → WARNING: Assets load globally when only needed on specific pages

### Transients & Options
Scan for:
- `set_transient` with dynamic keys (e.g., `user_{$id}`) → WARNING: Table bloat without object cache
- `set_transient` for frequently-changing data → WARNING: Defeats caching purpose
- Large data in transients on shared hosting → WARNING: DB bloat without object cache

### WP-Cron
Scan for:
- Missing `DISABLE_WP_CRON` constant → INFO: Cron runs on page requests
- Long-running cron callbacks (loops over all users/posts) → CRITICAL: Blocks cron queue
- `wp_schedule_event` without checking if already scheduled → WARNING: Duplicate schedules

## Search Patterns for Quick Detection

```bash
# Critical issues - scan these first
grep -rn "posts_per_page.*-1\|numberposts.*-1" .
grep -rn "query_posts\s*(" .
grep -rn "session_start\s*(" .
grep -rn "setInterval.*fetch\|setInterval.*ajax\|setInterval.*\\\$\." .

# Database writes on frontend
grep -rn "update_option\|add_option" . | grep -v "admin\|activate\|install"

# Uncached expensive functions
grep -rn "url_to_postid\|attachment_url_to_postid\|count_user_posts" .

# External HTTP without caching
grep -rn "wp_remote_get\|wp_remote_post\|file_get_contents.*http" .

# Cache bypass risks
grep -rn "setcookie\|session_start" .

# PHP code anti-patterns
grep -rn "in_array\s*(" . | grep -v "true\s*)" # Missing strict comparison
grep -rn "<<<" .  # Heredoc/nowdoc syntax
grep -rn "cache_results.*false" .

# JavaScript bundle issues
grep -rn "import.*from.*lodash['\"]" .  # Full lodash import
grep -rn "registerBlockStyle" .  # Many block styles = performance issue

# Asset loading issues
grep -rn "wp_enqueue_script\|wp_enqueue_style" . | grep -v "is_page\|is_singular\|is_admin"

# Transient misuse
grep -rn "set_transient.*\\\$" .  # Dynamic transient keys
grep -rn "set_transient" . | grep -v "get_transient"  # Set without checking first

# WP-Cron issues
grep -rn "wp_schedule_event" . | grep -v "wp_next_scheduled"  # Missing schedule check
```

## Platform Context

Different hosting environments require different approaches:

**Managed WordPress Hosts** (WP Engine, Pantheon, Pressable, WordPress VIP, etc.):
- Often provide object caching out of the box
- May have platform-specific helper functions (e.g., `wpcom_vip_*` on VIP)
- Check host documentation for recommended patterns

**Self-Hosted / Standard Hosting**:
- Implement object caching wrappers manually for expensive functions
- Consider Redis or Memcached plugins for persistent object cache
- More responsibility for caching layer configuration

**Shared Hosting**:
- Be extra cautious about unbounded queries and external HTTP
- Limited resources mean performance issues surface faster
- May lack persistent object cache entirely

## Quick Reference: Critical Anti-Patterns

### Database Queries
```php
// ❌ CRITICAL: Unbounded query.
'posts_per_page' => -1

// ✅ GOOD: Set reasonable limit, paginate if needed.
'posts_per_page' => 100,
'no_found_rows'  => true, // Skip count if not paginating.

// ❌ CRITICAL: Never use query_posts().
query_posts( 'cat=1' ); // Breaks pagination, conditionals.

// ✅ GOOD: Use WP_Query or pre_get_posts filter.
$query = new WP_Query( array( 'cat' => 1 ) );
// Or modify main query:
add_action( 'pre_get_posts', function( $query ) {
    if ( $query->is_main_query() && ! is_admin() ) {
        $query->set( 'cat', 1 );
    }
} );

// ❌ CRITICAL: Missing WHERE clause (falsy ID becomes 0).
$query = new WP_Query( array( 'p' => intval( $maybe_false_id ) ) );

// ✅ GOOD: Validate ID before querying.
if ( ! empty( $maybe_false_id ) ) {
    $query = new WP_Query( array( 'p' => intval( $maybe_false_id ) ) );
}

// ❌ WARNING: LIKE with leading wildcard (full table scan).
$wpdb->get_results( "SELECT * FROM wp_posts WHERE post_title LIKE '%term%'" );

// ✅ GOOD: Use trailing wildcard only, or use WP_Query 's' parameter.
$wpdb->get_results( $wpdb->prepare(
    "SELECT * FROM wp_posts WHERE post_title LIKE %s",
    $wpdb->esc_like( $term ) . '%'
) );

// ❌ WARNING: NOT IN queries (filter in PHP instead).
'post__not_in' => $excluded_ids

// ✅ GOOD: Fetch all, filter in PHP (faster for large exclusion lists).
$posts = get_posts( array( 'posts_per_page' => 100 ) );
$posts = array_filter( $posts, function( $post ) use ( $excluded_ids ) {
    return ! in_array( $post->ID, $excluded_ids, true );
} );
```

### Hooks & Actions
```php
// ❌ WARNING

Related in Web Dev