Glad that you managed to figure out a solution, which yes, does work. But you could make it more selectively, i.e. target just the specific pages, by using is_single( '<parent slug>/<child slug>' )
, e.g. is_single( 'family-law/success-stories' )
in your case.
Secondly, it’s not exactly the redirect_canonical
hook which caused the (301) redirect on singular paginated post pages (with URL having the /page/<number>
). Instead, it’s the $addl_path
part in the redirect_canonical()
function which fires the redirect_canonical
hook. More specifically, that part runs only if ! is_single()
is true (and that the current page number is 2 or more), and thus:
-
If the request (i.e. current page) URL was for a CPT at
example.com/practice-areas/family-law/success-stories/page/2/
wherepractice-areas
is the CPT slug andsuccess-stories
is child offamily-law
, the redirect/canonical URL would not containpage/2/
because! is_single()
is false, hence$addl_path
would be empty.The same scenario would also happen on
example.com/practice-areas/family-law/page/2/
(i.e. page 2 of the parent post) and other singular CPT pages, including regular posts (in the defaultpost
type), e.g. atexample.com/hello-world/page/2/
, but not on singular Pages (post typepage
) because! is_single()
is false — becauseis_single()
works with any post type, exceptattachment
andpage
. -
So if the request URL does not match the canonical URL, then WordPress runs the (301) redirect, which explains why this happened: “everytime I try to navigate to
/page/child-page/page/2/
WordPress sends me back to/page/child-page/
with a 301 redirect“.
Also, redirect_canonical()
is hooked on template_redirect
, so instead of having the function runs the various logic of determining the redirect/canonical URL and yet you eventually return an empty string, you might better off just disable the function like so:
add_action( 'template_redirect', 'my_template_redirect', 1 );
function my_template_redirect() {
if ( is_paged() && is_single( 'family-law/success-stories' ) ) {
remove_action( 'template_redirect', 'redirect_canonical' );
}
}
And yes, no extra rewrite rules are needed because WordPress already generated them (which handle the /page/<number>
) for your CPT. But I don’t know for sure why WordPress does the above ! is_single()
check. 🤔
Last but not least, in my original answer and comment as well, I suggested you to remove the $temp_query
parts, and your reply was: “I’m gonna keep the $temp_query bit though, since it allows our pagination component to work without the need to rewrite or complicate things” and “I think you were just saying it’s not needed“.
So yes, you’re right with that second sentence. But more precisely, I actually tested your template code where I put it in single-practice-areas.php
and then removed the $temp_query
parts (and used $total_pages = $practice_area_query->max_num_pages;
), and the /page/<number>
pagination worked just fine for me with the help of the above my_template_redirect()
function.
So I wondered what exactly does the $temp_query
fix, or why must you assign the $wp_query
to $practice_area_query
?
But if you really must keep it, then you need to move the wp_reset_postdata();
to after the $wp_query = $temp_query;
, i.e. after you restore the global $wp_query
variable back to the actual main query. Because otherwise, if for example you run the_title()
, you would see the title of the last post in your custom query and not the current (and the only) one in the main query.