Hi!
I encountered a conflict between Themeco’s Custom 404 plugin and Yoast SEO. I’ve spent a few hours investigating it and found a few different workarounds. It seems like the conflict is due to a WordPress bug, or at least some WordPress behavior that I don’t understand. But I’m posting it here to see what you think. One of the workarounds involves a small change to the Custom 404 plugin’s behavior.
Steps to reproduce:
- Create a new WordPress site.
- Install Pro and Pro – Child Theme. Activate the latter.
- Install and activate Custom 404.
- Install and activate Yoast SEO.
- Create and configure a custom 404 page.
- Go to Settings > Permalinks > Common Settings. Choose Custom Structure. Set it to something like
/blog/%postname%/
. - Try to visit a path that doesn’t match that structure, such as
/asfgd789gua89g/
.
Expected result:
The <title>
tag on the 404 page contains title of the custom 404 page.
Actual result:
The <title>
tag on the 404 page contains title of the most recent blog post.
Here’s what I found while investigating.
-
WordPress compares the requested path (for example,
/asfgd789gua89g/
) to a list of regular expressions generated by$wp_rewrite->wp_rewrite_rules()
. (Seewp-includes/class-wp.php
line 227.) -
The requested path doesn’t match any of the rules, so
$this->query_vars['error']
gets set to the default$error
value, which is'404'
. (Seewp-includes/class-wp.php
lines 166 and 379.) -
WordPress should know that it’s impossible to find the requested path, but it still calls
$wp_query->get_posts()
as usual while preparing its response. It ends up executing a query to find all$queried_post_types
. By default, this is equal toarray( 'post' )
, so it does a query for all blog posts. (Seewp-includes/class-wp-query.php
line 2555.) This seems like a bug to me, but maybe there’s a reason for it? It then sets$wp_query->posts
and$wp_query->post
as usual. (Seewp-includes/class-wp-query.php
line 3326.)- If the path had matched one of the rewrite rules, but
$wp_query->get_posts()
hadn’t found a corresponding post in the database,$wp_query->posts
would have been empty, and$wp_query->post
would have beenNULL
.
- If the path had matched one of the rewrite rules, but
-
The Custom 404 plugin replaces
$post
and some attributes of$wp_query
with values appropriate for the 404 page. But notably, it does not change$wp_query->post
. (Seetco-custom-404/views/site/custom-404.php
lines 46 through 56.) -
Before Yoast SEO tries to get the SEO title, it calls
wp_reset_query()
“to ensure we actually have the current page”. (Seewordpress-seo/src/memoizers/meta-tags-context-memoizer.php
line 95.) This function calls$wp_query->reset_postdata()
, which sets$GLOBALS['post'] = $this->post
, but only if$this->post
isn’t empty. (Seewp-includes/class-wp-query.php
line 4570.) So the title it fetches is the title of the original$wp_query->post
result, not the 404 page specified by Custom 404.
Workarounds
Pick one:
-
Change the Permalinks setting to Post Name so that arbitrary paths match a rewrite rule. (Not ideal because it changes the URLs of existing pages.)
-
Add a
posts_results
filter that returns an empty array if$GLOBALS['wp_query']->query['error'] === '404'
. (I haven’t tested this thoroughly for side-effects.) -
Add a line in
tco-custom-404/views/site/custom-404.php
that sets$wp_query->post
equal to$post
. This will prevent$wp_query->reset_postdata()
from changing$GLOBALS['post']
to something other than the 404 page. (But is there a reason why you didn’t include this line in the first place?)- This workaround can also be accomplished by adding a
404_template
filter that runs aftertco_custom_404_filter_template()
and sets$GLOBALS['wp_query']->post = $GLOBALS['post']
.
- This workaround can also be accomplished by adding a
Please let me know if you need any more information.
Thanks!
PHP 8.0.20
WordPress 6.0.1
Pro 5.1.5
Yoast SEO 19.5.1