GridPane gives you a great starting point: a fast server, page caching, Redis. What’s missing is the last 20% that decides whether a WooCommerce shop is actually fast for the traffic you pay for and correct for the customers who buy. That last 20% is what this article is about.
This is the setup we now run on our GridPane WooCommerce sites:
| What | Cached where | Why |
|---|---|---|
| normal pages | Redis on the server (not FastCGI) | clearing one site’s cache doesn’t touch the others |
ad traffic (gclid, utm_*, fbclid) | the same entry as the clean URL | the visitor from the ad gets the fast page, attribution keeps working |
| cart, checkout, account (under any name) | nowhere | a cached cart can show a customer someone else’s cart |
| price or stock change | product page refreshed right away | otherwise the shop shows the old price |
| CSS, JS, fonts, images | Cloudflare | that’s what a CDN is for |
| all-in-one speed plugin (FlyingPress, WP Rocket…) | none | we fix the theme and plugins at the source |
All of it is open source: SHIFT64/gridpane-nginx-marketing-params. An installer, a test suite, ten releases in sixteen days, and it runs on our production shops.
Table of Contents
Why Not FlyingPress or WP Rocket
Because we start from a simple rule: the theme and plugins should be fast at the source, not made to look fast by another plugin.
All-in-one speed plugins bundle two things: a page cache and front-end rewriting (delaying JavaScript, stripping CSS, combining files). The second part tends to hide problems rather than solve them. A theme that loads 1.2 MB of JavaScript is still loading it, just later, once Lighthouse has stopped looking. And when the plugin misjudges a payment button’s script, you’re debugging two layers instead of one.
GridPane already has a page cache, on the server, before WordPress even starts. So: no all-in-one plugin. That doesn’t mean zero plugins. WordPress still has to say when something changed, and that’s a job for small, single-purpose helpers: GridPane’s Nginx Helper clears the cache, and our small add-on (more below) covers what Nginx Helper doesn’t see in WooCommerce. Neither of them rewrites your front end.
You can see it straight in the logs. The same category page on a production shop, requested three times in a row (address and IP replaced, times exactly as logged):
[23/Oct/2026:16:02:31 +0200] 203.0.113.10 0.277 - shop.example "GET /category/lamps/ HTTP/2.0" 200 36377 0.319
[23/Oct/2026:16:02:31 +0200] 203.0.113.10 - - shop.example "GET /category/lamps/ HTTP/2.0" 200 36377 0.002
[23/Oct/2026:16:02:32 +0200] 203.0.113.10 - - shop.example "GET /category/lamps/ HTTP/2.0" 200 36377 0.001
- The third column is how long PHP worked. First request: 0.277 s. WordPress built the page and the server saved it to Redis. Second and third: a dash. PHP was never called.
- The last number is the total response time: 0.319 s, then 0.002 s and 0.001 s. That’s 160 to 300 times faster.
- Same byte count (36377) on all three, so it really is the same page, not an error or a redirect.
That’s the whole point: cached pages don’t use PHP at all. PHP workers stay free for what no cache can serve, like the cart, checkout and admin. A wave of campaign traffic or bots has nothing to clog them with, so the shop stays available to the people who are buying. And bots can arrive in a serious wave: yesterday one of our shops got more than 70,000 requests from Meta’s bot in 3 hours.
Redis, Not FastCGI
GridPane offers two kinds of page cache: FastCGI and Redis. For a single site they’re about equally fast. On a server with several sites there’s one big difference, and it’s about clearing. With FastCGI, each site has its own cache entries and never sees anyone else’s, and refreshing a single URL (say, after editing a post) only affects that site. But all sites live in one shared cache space on the server. When any of them does a “clear everything” (after a theme change, a plugin update, a deploy), it empties that whole space, which means every site’s cache. That’s GridPane’s default. You can work around it, but it takes more tinkering and more upkeep afterwards.
We caught this live. On one server, a staging site cleared the whole cache after every deploy, 1 to 11 times a day. Each time, the production shop next to it lost its entire cache and had to rebuild every page from scratch. With ten sites on one server, the cache disappears many times a day, for everyone at once, and the server takes a load spike exactly when every site is rebuilding.
With Redis, each site has its own database and clearing stays inside it. Out of the box, by default, with no custom changes and no extra upkeep. We tested that on two servers, one of them with 27 sites: clearing one site never touched another site’s cache. Details and measurements are in issue #1.
One caveat: sites share Redis memory, so one very large site can push out smaller sites’ entries. That’s still nothing like “every staging deploy wipes production”. So on every server with more than one site: Redis.
Ad Traffic: From Cache, Attribution Intact
Your ad sends people to an address like:
/product/mug/?gclid=EAIaIQobChMI...&utm_source=google
To the customer it’s the same mug. To the cache it’s a different address. The vast majority of cache setups, not just on GridPane, don’t cache any URL that has even a single query parameter. So that visitor skips the cache and waits for WordPress to build a page that’s already sitting ready under /product/mug/. On a live site we measured:
Live GridPane Redis site, WordPress 7.1, ab -k -c 8. Approximate values.
About a hundred times more throughput for the traffic you pay the most for.
Ignoring marketing parameters has been on GridPane’s roadmap for quite a while. We finally decided to stop waiting and build it our own way.
The simplest approach is to redirect visitors to the clean URL. But then the parameters vanish from the address bar, and that’s exactly where GA4, Google Ads and the Meta Pixel read them. Speed in exchange for broken attribution. Our fix works differently:
- No redirect. The parameters stay in the address bar, so attribution works as before.
- One cache entry. The visitor from the ad gets the same ready-made page as everyone else.
- WordPress sees the clean URL. Some pages copy parameters from the address into their content (WordPress’s own comment form does). If WordPress saw the first visitor’s
gclid, that value would be cached and served to everyone after them. - Redirects keep the parameters. When the shop redirects a visitor (say, to the URL with a trailing slash), the parameters go along.
- Everything else works as before. Search or add-to-cart links still go straight to WordPress.
Only parameters read by analytics and pixels in the browser go on the list. Never the ones the shop itself uses, like ref, search or add-to-cart.
If you’ve read our earlier piece on stripping UTM parameters with a Cloudflare Worker: that’s still the answer when you don’t control the server. On GridPane we do, so the server handles it, and none of our GridPane sites uses the Worker any more.
What We Never Cache
The standard rules recognise the cart, checkout and account pages by their English addresses: /cart, /checkout, /my-account. On a Polish shop whose cart lives at /koszyk/, we found the cart page in the cache. A guest without cookies could get another guest’s stored cart. The same goes for the REST API, which WooCommerce’s newer cart and checkout use.
The fix: WooCommerce itself now decides whether a page is the cart, whatever it’s called and in whatever language. Such a page never gets cached. Neither does the REST API.
Refreshing Pages When Prices and Stock Change
The same small add-on fixes the opposite problem: pages that should refresh and don’t.
WooCommerce saves price and stock changes in a way Nginx Helper doesn’t notice: stock dropping after an order, ERP syncs, imports, bulk edits, scheduled sales. On a live shop, over five days, 53 products and 22 variations changed, and the cache didn’t refresh once. Product pages showed the old price or “in stock” until the cache expired.
Meanwhile, every order status change cleared the home page, about 190 times a day. The very page most ads land on.
The add-on turns that around:
- a price or availability change refreshes the product, its categories, the shop page and the home page;
- a change in quantity alone, with the same availability, refreshes nothing by default, as long as the shop doesn’t show stock levels on the product page. If it does (“12 left” → “11 left”), one filter turns refreshing on for quantity too;
- orders, refunds and order notes clear nothing;
- clearing happens after the response is sent, so a customer at checkout never waits for it.
Cloudflare: Static Files Only
If Cloudflare sits in front, it keeps only CSS, JS, fonts and images. HTML pages stay in Redis on the server, where they refresh the moment content or a price changes. No stale copies at Cloudflare, and no plugin to manage them.
Bonus: Answers Straight from the Logs
When caching and cache clearing happen on the server, every visit and every clear leaves a trace in the nginx logs. No guessing, and no extra stats plugin. You open the logs and count, just like in the excerpt above.
Every number in this article came from there: a staging site wiping production’s cache 1 to 11 times a day, about 190 home-page clears a day, 53 changed products without a single refresh. With an all-in-one plugin, cache decisions happen inside the plugin and are much harder to trace.
Battle-Tested
The first version shipped on 7 October 2026, the current one on 23 October. Almost every one of the ten releases came from something we saw in production: a neighbouring site wiping the cache, /koszyk/ in the cache, prices that never refreshed.
It’s safe to use on a live server because:
- the tests run on a throw-away copy using GridPane’s real templates from your server, and never touch the live sites;
- the installer checks the configuration before every change and rolls back if anything’s wrong;
- a live test checks your site after installing;
- cases it can’t handle safely are skipped, with the reason.
Install, on the GridPane server as root:
git clone https://github.com/SHIFT64/gridpane-nginx-marketing-params.git /root/gridpane-nginx-marketing-params
cd /root/gridpane-nginx-marketing-params && ./test.sh && ./install.sh --full yourshop.com
./test.sh live yourshop.com /some-page/ --fresh
The full technical details are in the README.
What You Give Up
- Listed parameters don’t reach WordPress on cached pages. If a landing page changes its content server-side based on
utm_campaign, it will stop doing that. (On a cached page it couldn’t do it reliably anyway.) - You maintain it. After GridPane updates, run the tests; they’ll tell you if anything changed.
Summary
Redis instead of FastCGI, ad traffic served from cache, the cart never cached, prices refreshed right away, Cloudflare for static files only, and no all-in-one plugin. That’s what our GridPane shops run on today.
It isn’t a one-off configuration. We keep improving and monitoring it, and every new release comes from what we see in the logs.
If you have a WooCommerce shop that needs fine-tuning, get in touch. We’ll do it.