Created
April 10, 2026 11:31
-
-
Save vapvarun/367e2c14913235924db676b376fb78ee to your computer and use it in GitHub Desktop.
WooCommerce Caching Strategies That Work with Dynamic Cart Data (woocustomdev.com)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ## nginx.conf — Page cache with WooCommerce cart/session exclusions | |
| ## Place inside your server {} block (or FastCGI Cache config) | |
| fastcgi_cache_path /tmp/nginx-cache levels=1:2 keys_zone=WORDPRESS:100m inactive=60m; | |
| fastcgi_cache_key "$scheme$request_method$host$request_uri"; | |
| server { | |
| # ... your existing server config ... | |
| set $skip_cache 0; | |
| # Skip cache for POST requests | |
| if ($request_method = POST) { | |
| set $skip_cache 1; | |
| } | |
| # Skip cache when query string is present | |
| if ($query_string != "") { | |
| set $skip_cache 1; | |
| } | |
| # Skip cache for WooCommerce dynamic pages | |
| if ($request_uri ~* "(/cart|/checkout|/my-account|/wc-api|/addons|/store|/shop.*add-to-cart=)") { | |
| set $skip_cache 1; | |
| } | |
| # Skip cache when WooCommerce session cookies are present | |
| if ($http_cookie ~* "comment_author|wordpress_[a-f0-9]+|wp-postpass|woocommerce_items_in_cart|woocommerce_cart_hash|wp_woocommerce_session") { | |
| set $skip_cache 1; | |
| } | |
| location ~ \.php$ { | |
| fastcgi_cache WORDPRESS; | |
| fastcgi_cache_valid 200 60m; | |
| fastcgi_cache_bypass $skip_cache; | |
| fastcgi_no_cache $skip_cache; | |
| add_header X-FastCGI-Cache $upstream_cache_status; | |
| fastcgi_pass unix:/run/php/php8.2-fpm.sock; | |
| include fastcgi_params; | |
| fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * wp-config.php additions — Redis object cache configuration | |
| * Requires: Redis server + redis PHP extension (or predis) + wp-redis drop-in | |
| * | |
| * Install the drop-in: | |
| * wp plugin install wp-redis --activate | |
| * wp redis enable | |
| */ | |
| // Redis connection settings | |
| define( 'WP_REDIS_HOST', '127.0.0.1' ); | |
| define( 'WP_REDIS_PORT', 6379 ); | |
| define( 'WP_REDIS_DATABASE', 0 ); | |
| define( 'WP_REDIS_TIMEOUT', 1 ); | |
| define( 'WP_REDIS_READ_TIMEOUT', 1 ); | |
| // Prefix all keys with the site name to support multi-site Redis instances | |
| define( 'WP_REDIS_PREFIX', 'woocustomdev_' ); | |
| // Groups that should NEVER be cached in Redis (volatile WooCommerce data) | |
| define( 'WP_REDIS_IGNORED_GROUPS', [ | |
| 'counts', | |
| 'plugins', | |
| 'wc_session_id', | |
| 'wc_cart_', | |
| ] ); | |
| // Selective group flushing: flush only wc_* keys on cart updates | |
| // (requires object-cache.php that supports group-level flushing, e.g., wp-redis 1.7+) | |
| define( 'WP_REDIS_SELECTIVE_FLUSH', true ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Fragment caching for WooCommerce — cache expensive partial outputs as transients. | |
| * | |
| * Use case: cache a "Featured Products" section that runs 4-5 DB queries, | |
| * while leaving the mini-cart (dynamic) outside the cached fragment. | |
| */ | |
| /** | |
| * Output a cached product loop fragment. | |
| * | |
| * @param string $fragment_key Unique identifier for this fragment. | |
| * @param callable $callback The expensive rendering callback. | |
| * @param int $expiry Cache duration in seconds (default 1 hour). | |
| */ | |
| function wcd_fragment_cache( string $fragment_key, callable $callback, int $expiry = HOUR_IN_SECONDS ): void { | |
| $transient_key = 'wcd_frag_' . md5( $fragment_key ); | |
| $cached = get_transient( $transient_key ); | |
| if ( false !== $cached ) { | |
| echo $cached; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped | |
| return; | |
| } | |
| ob_start(); | |
| $callback(); | |
| $output = ob_get_clean(); | |
| set_transient( $transient_key, $output, $expiry ); | |
| echo $output; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped | |
| } | |
| /** | |
| * Invalidate a fragment when a product is saved. | |
| * Hooked to save_post_product so stale fragments are purged immediately. | |
| */ | |
| add_action( 'save_post_product', function ( int $post_id ): void { | |
| // Bust the featured products fragment whenever any product is updated. | |
| delete_transient( 'wcd_frag_' . md5( 'featured_products' ) ); | |
| } ); | |
| // --- Usage example in a theme template --- | |
| wcd_fragment_cache( | |
| 'featured_products', | |
| function (): void { | |
| $products = wc_get_products( [ | |
| 'featured' => true, | |
| 'limit' => 8, | |
| 'status' => 'publish', | |
| ] ); | |
| foreach ( $products as $product ) { | |
| echo '<div class="product-card">'; | |
| echo '<a href="' . esc_url( $product->get_permalink() ) . '">'; | |
| echo esc_html( $product->get_name() ); | |
| echo '</a>'; | |
| echo '</div>'; | |
| } | |
| } | |
| ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * WooCommerce AJAX cart handler — always bypasses page/fragment cache. | |
| * | |
| * Pattern: Register an AJAX endpoint that returns fresh cart HTML, | |
| * then update the DOM via JS. This keeps the rest of the page cached | |
| * while the cart stays accurate on every request. | |
| */ | |
| /** | |
| * Register the AJAX action for both logged-in and guest users. | |
| */ | |
| add_action( 'wp_ajax_wcd_get_cart_fragment', 'wcd_get_cart_fragment_handler' ); | |
| add_action( 'wp_ajax_nopriv_wcd_get_cart_fragment', 'wcd_get_cart_fragment_handler' ); | |
| /** | |
| * Return JSON with fresh cart HTML and item count. | |
| * Sets no-cache headers so proxies / CDNs never cache this response. | |
| */ | |
| function wcd_get_cart_fragment_handler(): void { | |
| // Hard no-cache headers — CDN / Varnish must NOT cache this endpoint. | |
| nocache_headers(); | |
| header( 'Surrogate-Control: no-store' ); | |
| WC()->cart->calculate_totals(); | |
| ob_start(); | |
| woocommerce_mini_cart(); | |
| $mini_cart_html = ob_get_clean(); | |
| wp_send_json_success( [ | |
| 'cart_html' => $mini_cart_html, | |
| 'cart_count' => WC()->cart->get_cart_contents_count(), | |
| 'cart_total' => WC()->cart->get_cart_total(), | |
| ] ); | |
| } | |
| /** | |
| * Enqueue the JS that polls for fresh cart data. | |
| * Uses wp_localize_script to pass nonce + ajaxurl safely. | |
| */ | |
| add_action( 'wp_enqueue_scripts', function (): void { | |
| wp_enqueue_script( | |
| 'wcd-cart-refresh', | |
| get_template_directory_uri() . '/assets/js/cart-refresh.js', | |
| [ 'jquery', 'wc-cart-fragments' ], | |
| '1.0.0', | |
| true | |
| ); | |
| wp_localize_script( 'wcd-cart-refresh', 'wcdCart', [ | |
| 'ajaxurl' => admin_url( 'admin-ajax.php' ), | |
| 'action' => 'wcd_get_cart_fragment', | |
| 'nonce' => wp_create_nonce( 'wcd_cart_nonce' ), | |
| ] ); | |
| } ); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| /** | |
| * Targeted cache invalidation for WooCommerce. | |
| * | |
| * Instead of flushing everything on every action, these hooks | |
| * purge only the URLs and object-cache groups that actually changed. | |
| */ | |
| /** | |
| * Purge the product page cache when stock changes. | |
| * Works with Nginx FastCGI cache and WP Rocket / LiteSpeed / W3TC. | |
| */ | |
| add_action( 'woocommerce_product_set_stock', function ( WC_Product $product ): void { | |
| $url = get_permalink( $product->get_id() ); | |
| // WP Rocket | |
| if ( function_exists( 'rocket_clean_post' ) ) { | |
| rocket_clean_post( $product->get_id() ); | |
| } | |
| // LiteSpeed Cache | |
| if ( class_exists( 'LiteSpeed_Cache_API' ) ) { | |
| LiteSpeed_Cache_API::purge_post( $product->get_id() ); | |
| } | |
| // W3 Total Cache | |
| if ( function_exists( 'w3tc_pgcache_flush_post' ) ) { | |
| w3tc_pgcache_flush_post( $product->get_id() ); | |
| } | |
| // Object cache: clear product transients | |
| wc_delete_product_transients( $product->get_id() ); | |
| // Optional: hit a custom Nginx purge endpoint | |
| if ( defined( 'WCD_NGINX_PURGE_URL' ) ) { | |
| wp_remote_get( WCD_NGINX_PURGE_URL . str_replace( home_url(), '', $url ) ); | |
| } | |
| } ); | |
| /** | |
| * Flush shop/category pages when a product status changes | |
| * (e.g., published → draft drops it from archive listings). | |
| */ | |
| add_action( 'transition_post_status', function ( string $new, string $old, WP_Post $post ): void { | |
| if ( 'product' !== $post->post_type || $new === $old ) { | |
| return; | |
| } | |
| // WP Rocket: flush the shop archive and all category pages | |
| if ( function_exists( 'rocket_clean_term' ) ) { | |
| $terms = get_the_terms( $post->ID, 'product_cat' ); | |
| if ( is_array( $terms ) ) { | |
| foreach ( $terms as $term ) { | |
| rocket_clean_term( $term, 'product_cat' ); | |
| } | |
| } | |
| } | |
| // Bust shop page object cache | |
| wp_cache_delete( 'woocommerce_product_ids', 'woocommerce' ); | |
| }, 10, 3 ); | |
| /** | |
| * Clear session-scoped object cache groups after checkout. | |
| * Prevents stale order totals showing up in My Account for a brief window. | |
| */ | |
| add_action( 'woocommerce_checkout_order_processed', function ( int $order_id ): void { | |
| wp_cache_delete( 'customer_get_last_order', 'woocommerce' ); | |
| wp_cache_flush_group( 'wc_orders' ); // WordPress 6.1+ / Redis 1.8+ | |
| wc_delete_shop_order_transients( $order_id ); | |
| } ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment