没有不值得去解决的问题,也没有不值得去学习的技术!

Optimize WordPress Category Dropdown Redirects: Skip ?category_name= and Link Directly to the Final URL

Figure 4: After the modification takes effect, selecting from the category dropdown list goes directly to the Bootstrap 5 final category permalink, without the category_name intermediate address appearing.

作者:

在

While re-checking the WordPress homepage recently, I noticed a familiar issue reappearing in the category dropdown list.

After selecting PHP Simple HTML DOM Parser from the category dropdown list on the right side of the homepage, the browser accessed:

Plaintext
https://www.shuijingwanwq.com/?category_name=php-simple-html-dom-parser

However, the page did not proceed to the actual category archive; instead, it still displayed the homepage content.

Figure 1: The category dropdown list still redirects via the category_name query parameter, and the current page does not enter the corresponding category archive.
Figure 1: The category dropdown list still redirects via the category_name query parameter, and the current page does not enter the corresponding category archive.

This phenomenon quickly reminded me of two previous adjustments: one was a multilingual domain fix for the category dropdown list, and the other was a cache optimization for CDN Query Strings.

These two features, originally functioning normally on their own, created a new conflict when combined later.

1. This category dropdown list was actually fixed once before

On July 13, 2026, after migrating the WordPress English site to an independent subdomain, I dealt with a category dropdown list redirect error.

The complete troubleshooting process at that time is documented in WordPress Polylang Links Still Point to Chinese Site After English Subdomain Migration: A Complete Fix for Category Dropdowns, Breadcrumbs, and Block Templates.

When publishing to WordPress, these body links are all set to “open in new tab”.

The most obvious issue back then was that after selecting the Browser category on the English homepage, even though the category slug belonged to the English category, the redirect URL still used the Chinese main domain. The article ultimately confirmed that there was no issue with the English category data or the official category permalink itself; the error mainly occurred in the redirect address generated by the category dropdown list.

The workflow after that fix remained:

Plaintext
Select category
↓
Generate /?category_name=category-slug
↓
WordPress parses category_name
↓
Redirect to final category permalink

For example:

Plaintext
https://en.shuijingwanwq.com/?category_name=browser-en

Then it would proceed to:

Plaintext
https://en.shuijingwanwq.com/category/application-tool-en/browser-en/

This approach worked fine at the time.

2. The subsequent Query String optimization exposed this problem again

On August 3, 2026, I performed a Query String normalization optimization for WordPress page caching and CDN Cache Keys.

The relevant process is documented in From EdgeOne 524 to Query String Normalization: WordPress Posts and Public List Pages CDN Cache Optimization in Practice.

The core goal of this optimization was to prevent query parameters that do not change page content from continuously generating new cache versions.

For example, for standard public pages:

Plaintext
/

And:

Plaintext
/?random=123

If the final page content is exactly the same, there is no need to maintain separate page caches.

There was nothing wrong with this optimization direction itself.

But after re-testing the category dropdown list, I realized:

Plaintext
category_name

is not just a meaningless query string.

It serves the actual redirect function for the WordPress category dropdown list.

This led to the following conflict chain:

Plaintext
Category dropdown list
↓
Generates ?category_name=slug
↓
CDN Query String normalization
↓
category_name no longer affects the request as it originally did
↓
WordPress receives an effect similar to a standard homepage

The initially obvious fix was to continue modifying EdgeOne or Cloudflare rules to include:

Plaintext
category_name

among the functional query strings that need to be protected.

But thinking one step further, I realized there was a simpler question:

Why must the category dropdown list go through ?category_name= first?

3. Since we already know the final address, why not redirect directly?

What WordPress categories actually need to access is the standard category permalink.

For example, the final address for PHP Simple HTML DOM Parser looks like:

Plaintext
/category/web-devel/html-parser/php-simple-html-dom-parser/

So the current chain:

Plaintext
Select category
↓
/?category_name=slug
↓
WordPress parses again
↓
Final category address

can be completely simplified to:

Plaintext
Select category
↓
Final category address

The benefits of this are straightforward.

First, it eliminates an intermediate request.

Second, the category dropdown list no longer relies on Query Strings.

Third, it is completely decoupled from the CDN’s Query String caching rules.

Fourth, category archives are inherently standard permalink formats, making subsequent caching strategies much more intuitive.

However, we cannot simply construct the URL ourselves based on the slug:

Plaintext
/category/php-simple-html-dom-parser/

Because my WordPress categories have a parent-child hierarchy.

The actual address might be:

Plaintext
/category/web-devel/html-parser/php-simple-html-dom-parser/

Therefore, the final URL should still be generated by WordPress itself, rather than guessing the path in JavaScript.

4. Continue using WPCode without modifying Twenty Twenty-Five

This time, I chose to use WPCode to complete the adjustment, rather than directly modifying the Twenty Twenty-Five theme files.

The original code snippet name was:

Plaintext
Fix category dropdown list redirect domain for non-default language sites

Its primary responsibility was to fix the issue found on July 13, where the English site redirected back to the Chinese main domain.

Now that the functionality has changed, I renamed it to:

Plaintext
Category dropdown list direct redirect to final category link

I did not create a second code snippet, but directly modified the original code.

This avoids having two JavaScript snippets listening to the same category dropdown list simultaneously.

Figure 2: Continue using the original WPCode code snippet, upgrading the functionality to directly access the final category permalink.
Figure 2: Continue using the original WPCode code snippet, upgrading the functionality to directly access the final category permalink.

5. Leave page scope to WPCode as much as possible, instead of piling up PHP conditionals

During this adjustment, I also continued using a WPCode workflow that I have preferred lately:

If page checks can be handled via Location and Smart Conditional Logic, do not duplicate them in PHP.

The code snippet uses:

Plaintext
Code Type:
PHP Snippet

Insert Method:
Auto Insert

Location:
Frontend Conditional Logic

Initially, I considered restricting it to the Homepage only.

But upon inspection, I found that the category dropdown list exists not only on the homepage but also on archive pages like category pages.

Therefore, the final configuration is:

Plaintext
Type of Page → Is → Homepage

OR

Type of Page → Is → Archive
Figure 3: The code snippet executes only on the homepage or archive pages, leaving page scope management to the WPCode configuration layer.
Figure 3: The code snippet executes only on the homepage or archive pages, leaving page scope management to the WPCode configuration layer.

This way, there is no need to maintain page conditionals like:

PHP
is_front_page()
is_home()
is_archive()
is_admin()

in the PHP code.

WPCode is responsible for answering:

Which pages need to execute this?

The PHP code is only responsible for answering:

How should the category dropdown list redirect directly to the final URL?

This makes the responsibilities much clearer.

6. Final code

The final WPCode used is as follows:

PHP
/**
 * SWQ:分类下拉列表直接跳转最终分类链接。
 */
add_filter(
    'render_block_core/categories',
    function ( $block_content, $block ) {

        if ( empty( $block['attrs']['displayAsDropdown'] ) ) {
            return $block_content;
        }

        $processor = new WP_HTML_Tag_Processor( $block_content );

        while ( $processor->next_tag( 'option' ) ) {
            $slug = $processor->get_attribute( 'value' );

            if ( empty( $slug ) || '-1' === $slug ) {
                continue;
            }

            $term = get_term_by( 'slug', $slug, 'category' );

            if ( ! $term || is_wp_error( $term ) ) {
                continue;
            }

            $url = get_term_link( $term );

            if ( is_wp_error( $url ) ) {
                continue;
            }

            $processor->set_attribute(
                'data-swq-term-url',
                $url
            );
        }

        $block_content = $processor->get_updated_html();

        static $script_added = false;

        if ( ! $script_added ) {
            $script_added = true;

            $block_content .= <<<'HTML'
<script>
document.addEventListener('change', function (event) {
    const dropdown = event.target;

    if (!(dropdown instanceof HTMLSelectElement)) {
        return;
    }

    const option = dropdown.options[dropdown.selectedIndex];
    const url = option?.dataset.swqTermUrl;

    if (!url) {
        return;
    }

    event.stopPropagation();
    window.location.assign(url);
}, true);
</script>
HTML;
        }

        return $block_content;
    },
    20,
    2
);

Here, I did not crudely change the original value of <option> from a slug to a URL.

The original structure is preserved, for example:

HTML
<option value="php-simple-html-dom-parser">

During rendering, the final category URL is simply added, roughly like:

HTML
<option
    value="php-simple-html-dom-parser"
    data-swq-term-url="https://www.shuijingwanwq.com/category/web-devel/html-parser/php-simple-html-dom-parser/"
>

When the user re-selects a category, JavaScript reads:

Plaintext
data-swq-term-url

Then:

JavaScript
window.location.assign(url);

It goes directly to the final address.

Most importantly, the final URL comes from:

PHP
get_term_link()

rather than being constructed manually.

This way, the parent-child category structure, category slugs, and the respective domains of the Chinese and English sites continue to be handled by WordPress and the existing multilingual system.

7. This time, I did not rush to clear the cache

After modifying WPCode, I did not immediately clear all page caches and CDN caches.

Because the old pages were already cached, manually clearing all caches immediately would make it difficult to observe the actual results received by visitors after a natural cache lifecycle.

Therefore, this time I simply waited for the cache to expire naturally.

From completing the modifications on August 8 to re-checking on August 14, I did not continue modifying the code out of anxiety over test results.

Testing again a few days later, I saw the naturally updated pages.

8. Conducting four sets of practical validation on August 14

After the cache expired naturally, I re-tested four scenarios:

  1. Chinese homepage → select a category
  2. Chinese archive page → select a category
  3. English homepage → select a category
  4. English archive page → select a category

All four passed.

Now, when interacting with the category dropdown list normally, the browser no longer needs to go through:

Plaintext
/?category_name=xxx

but goes directly to the final category permalink.

For example, after selecting Bootstrap 5 on the Chinese archive page, the browser goes directly to:

Plaintext
https://www.shuijingwanwq.com/category/web/html-css/css-framework/bootstrap/bootstrap-5/
Figure 4: After the modification takes effect, selecting from the category dropdown list goes directly to the Bootstrap 5 final category permalink, without the category_name intermediate address appearing.
Figure 4: After the modification takes effect, selecting from the category dropdown list goes directly to the Bootstrap 5 final category permalink, without the category_name intermediate address appearing.

This is the core validation result of this modification.

9. Ultimately, EdgeOne and Cloudflare were not modified for this

When the problem was first discovered, there was another entirely feasible approach:

Plaintext
Keep ?category_name=
+
Add category_name to the functional Query String exceptions in the CDN

But ultimately, I did not do this.

The reason is simple.

After the new category dropdown logic took effect, normal users no longer access categories via:

Plaintext
/?category_name=xxx

Since the business flow no longer needs this intermediate query parameter, there is naturally no need to have EdgeOne and Cloudflare specifically add an exception for it.

The responsibilities of the two layers ultimately became:

The two are no longer dependent on each other.

Plaintext
CDN
↓
Continues to handle existing Query String normalization and page caching

WordPress category dropdown list
↓
Directly accesses standard category permalinks

This is much simpler than continually expanding the Query String whitelist.

This is much simpler than continually expanding the Query String whitelist.

10. What was truly solved this time is more than just a redirect error

When I first saw:

Plaintext
/?category_name=php-simple-html-dom-parser

fail, my first thought was still:

Add another exception to the CDN.

This is a very natural line of thought when facing compatibility issues.

But the final solution this time felt more worth recording:

Rather than constantly patching exceptions for newly added rules, it is better to first check if there are intermediate layers in the old business flow that can simply be removed.

The original structure:

Plaintext
Category dropdown list
↓
category_name
↓
WordPress query parameter parsing
↓
Final category permalink

now becomes:

Plaintext
Category dropdown list
↓
Final category permalink

Less Query Strings, one less intermediate step, and one less layer of coupling with the CDN.

At the same time, this also further established a WPCode usage pattern I currently prefer:

Plaintext
Location / Enable Logic
↓
Responsible for "where to run"

PHP / JavaScript
↓
Only responsible for "what to run"

If page checks can be handled by the configuration interface, they should not be stuffed into the code.

This not only makes the code more concise, but also makes it easier to quickly understand its responsibilities when revisiting this code snippet months or even years later.

Summary

This WordPress category dropdown list issue initially appeared to be just a compatibility regression caused by Query String caching strategies.

But ultimately, it was not fixed by adding:

Plaintext
category_name

as a cache exception.

Instead, the entire category redirect flow was further simplified to:

Plaintext
WPCode
+
render_block_core/categories
+
WP_HTML_Tag_Processor
+
get_term_link()
+
JavaScript direct redirect to final URL

The code snippet is restricted by WPCode to:

Plaintext
Homepage OR Archive

After waiting for the cache to expire naturally, all four scenarios—Chinese homepage, Chinese archive page, English homepage, and English archive page—passed validation.

At this point, normal category dropdown list access no longer uses the following Query String:

Plaintext
?category_name=

Now, the category dropdown list can directly enter the final /category/…/ permalink.

This optimization appears to just remove a Query String, but what it actually reduces is the unnecessary coupling between business redirect logic and CDN caching rules.

系列导航

关于作者 · 持续学习与技术实践

我是一名拥有 15 年以上开发经验的技术从业者,自 2013 年起持续运营个人技术博客,记录工作与个人项目中遇到的真实问题,以及解决问题过程中的探索、实践和思考。

除了技术写作,我也在持续开发和维护自己的独立项目,探索新技术和新工具在实际场景中的应用。我始终相信,没有不值得去解决的问题,也没有不值得去学习的技术。

如果这篇文章对你有所帮助,欢迎浏览博客中的其他文章,也欢迎交流技术经验与想法。

关于我  ·  GitHub  ·  邮件联系