0

the theme I made didn't have a search functionality. I started receiving in Search Console thousands of soft 404 for similar pages:

https://www.example.com/?s={search_term_string}page/3page/2page/3page/2page/3page/3page/3page/2page/2page/3page/3page/3page/2page/3page/2

So I created a new search.php page. The pagination is working fine, it is showing the right amount of pages and it paginates well.

However the links are wrong.

Page 1 is: https://www.example.com/?s=therapy - which is good Then I click on page 2: Page 2 is: https://www.example.com/?s=therapy&paged=2/page/2/ - which is not the format I want, but it works. Page 3 is https://www.example.com/?s=therapy&paged=3/page/3/ - same situation If the page "1" button now has link https://www.example.com//page/1/ - which is wrong and redirects to https://www.example.com

Questions: How to make the links of page 2 and next pages to have a working structure of https://www.example.com/?s=therapy/page/3/?

How to make the link to page one be https://www.example.com/?s=therapy?

This is the code I have:

<?php
get_header();

$args_booking = array(
    'posts_per_page' => 2,
    'post_type' => 'post',
    'paged' => get_query_var('paged') ? get_query_var('paged') : 1
);

$query = new WP_Query($args_booking);
?>

[post loop here]

<div class="pagination">
                <?php 
                    $total_pages = $wp_query->max_num_pages;
                    if ($total_pages > 1) :
                        $current_page = max(1, get_query_var('paged'));
                        $search_query = get_search_query();
                        $base = esc_url(home_url('/')) . '%_%';
                        $base = user_trailingslashit($base) . 'page/%#%/';

                        echo paginate_links(array(
                            'base' => $base,
                            'format' => '?s=' . urlencode($search_query) . '&paged=%#%',
                            'current' => $current_page,
                            'total' => $total_pages,
                            'prev_text' => '‹',
                            'next_text' => '›',
                        ));
                    endif;
                ?>
            </div>
            <?php } ?>

If I change it this way, the URLs look fine, but the pagination doesn't work... which means that no matter the page, always the same posts are being shown.

<div class="pagination">
                <?php 
                    $total_pages = $wp_query->max_num_pages;
                    if ($total_pages > 1) :
                        $current_page = max(1, get_query_var('paged'));

                        echo paginate_links(array(
                            'base' => get_pagenum_link(1) . '%_%',
                            'format' => '/page/%#%',
                            'current' => $current_page,
                            'total' => $total_pages,
                            'prev_text' => '‹',
                            'next_text' => '›',
                        ));
                    endif;
                ?>
            </div>
            <?php } ?>

Can anyone please help?

Update. Here is the code after the changes suggested in the first answer:

<?php get_header(); ?>

<div class="wapper">
  <div class="contentarea clearfix">
    <div class="content">
      <h1 class="search-title"> <?php echo $wp_query->found_posts; ?>
        <?php _e( 'Search Results Found For', 'locale' ); ?>: "<?php the_search_query(); ?>" </h1>

        <?php if ( have_posts() ) { ?>

            <ul>
                <?php while ( have_posts() ) { the_post(); ?>
                <li>
                    <h3><a href="<?php echo get_permalink(); ?>">
                    <?php the_title(); ?>
                    </a></h3>
                    <?php the_post_thumbnail('medium') ?>
                    <?php echo substr(get_the_excerpt(), 0,200); ?>
                    <div class="h-readmore"> <a href="<?php the_permalink(); ?>">Read More</a></div>
                </li>
                <?php } ?>
            </ul>
            <?php paginate(); } ?>

    </div>
  </div>
</div>
<?php get_footer(); ?>

and code added to the bottom of functions.php:

/**
 * Override search query parameters
 * 
 * @param object $query An instance of WP_Query
 */
function wpse_424907( $query ) {
    if( $query-> is_main_query() && $query->is_search() ) {
      $query->set( 'posts_per_page', 2 );
      $query->set( 'post_type', 'post' );
      return;
    }
  }
  
  add_action( 'pre_get_posts', 'wpse_424907', 1 );

/**
 * Echo pagination. If used with a custom query, it needs to be passed as an argument. Otherwise it assumes the default $wp_query
 * 
 * @param object $query An instance of WP_Query
 */
function paginate($query = '') {
if (!($query instanceof WP_Query)) {
global $wp_query;
$query = $wp_query;
}
if ($query->max_num_pages > 1) {
$current_page = max(1, get_query_var('paged'));
echo '<nav class="pagination">';
echo paginate_links(array(
    'base' => get_pagenum_link(1) . '%_%',
    'format' => 'page/%#%',
    'current' => $current_page,
    'total' => $query->max_num_pages,
    'prev_text' => '‹',
    'next_text' => '›'
));
echo '</nav>';
}
}

1 Answer 1

1

It looks like you've attempted to replace the default WP_Query object for your search page by instantiating a new WP_Query object. This has two main effects: firstly you're hitting the database twice completely unnecessarily, slowing down the page execution; and secondly default template functionality like pagination depends on the default query for the page rather than the one you've run, so things break in unexpected ways.

What you've done there should only ever be used to add a supplementary loop to the page, it can't replace the default loop.

Remove this:

$args_booking = array(
    'posts_per_page' => 2,
    'post_type' => 'post',
    'paged' => get_query_var('paged') ? get_query_var('paged') : 1
);

$query = new WP_Query($args_booking);

The correct way to override a default query is by hooking to pre_get_posts in your theme's functions.php and altering it before it's sent to the DB.

/**
 * Override search query parameters
 * 
 * @param object $query An instance of WP_Query
 */
function wpse_424907( $query ) {
  if( $query-> is_main_query() && $query->is_search() ) {
    $query->set( 'posts_per_page', 2 );
    $query->set( 'post_type', 'post' );
    return;
  }
}

add_action( 'pre_get_posts', 'wpse_424907', 1 );

Your pagination is explicitly referring to $wp_query rather than the query you ran above, which is assigned to $query.

It would be a good idea to abstract the pagination into another function so you're not repeating the same logic in all your templates:

  /**
   * Echo pagination. If used with a custom query, it needs to be passed as an argument. Otherwise it assumes the default $wp_query
   * 
   * @param object $query An instance of WP_Query
   */
  function paginate($query = '') {
    if (!($query instanceof WP_Query)) {
      global $wp_query;
      $query = $wp_query;
    }
    if ($query->max_num_pages > 1) {
      $current_page = max(1, get_query_var('paged'));
      echo '<nav class="pagination">';
      echo paginate_links(array(
          'base' => get_pagenum_link(1) . '%_%',
          'format' => 'page/%#%',
          'current' => $current_page,
          'total' => $query->max_num_pages,
          'prev_text' => '‹',
          'next_text' => '›'
      ));
      echo '</nav>';
    }
  }

(Semantically, pagination should be a <nav> element).

That should also go in your functions.php so you can call it in other templates. Here's what your search template should look like now:

<?php get_header(); ?>

<div class="wapper">
  <div class="contentarea clearfix">
    <div class="content">
      <h1 class="search-title"> <?php echo $wp_query->found_posts; ?>
        <?php _e( 'Search Results Found For', 'locale' ); ?>: "<?php the_search_query(); ?>" </h1>

        <?php if ( have_posts() ) { ?>

            <ul>
                <?php while ( have_posts() ) { the_post(); ?>
                <li>
                    <h3><a href="<?php echo get_permalink(); ?>">
                    <?php the_title(); ?>
                    </a></h3>
                    <?php the_post_thumbnail('medium') ?>
                    <?php echo substr(get_the_excerpt(), 0,200); ?>
                    <div class="h-readmore"> <a href="<?php the_permalink(); ?>">Read More</a></div>
                </li>
                <?php } ?>
            </ul>
            <?php paginate(); ?>

    </div>
  </div>
</div>
<?php get_footer(); ?>

Since WordPress doesn't prettify search URLs by default, we'll also need to add a filter for that to functions.php:

/**
* Prettify the search permalinks
* 
* @return void
*/
function wpse_424907_search_url_rewrite() {
  if ( is_search() && ! empty( $_GET['s'] ) ) {
    wp_redirect( home_url( "/search/" ) . urlencode( get_query_var( 's' ) ) );
        exit();
  } 
}
add_action( 'template_redirect', 'wpse_424907_search_url_rewrite' );
10
  • Thank you! I applied these changes, the page load. The filter works. However the pagination doesn't appear. Probably because many search results are appearing. It seems that $query->set( 'posts_per_page', 2 ); is ignored. Do you have any idea? Or maybe I'll share the whole code in my search.php? (no errors in the debug.log)
    – Pikk
    Commented Apr 25 at 18:33
  • The whole page would definitely help.
    – Chris Cox
    Commented Apr 26 at 20:50
  • Full code added to the original question. Thank you in advance for your help
    – Pikk
    Commented Apr 26 at 22:39
  • The functions I supplied (and the add_action call) need to be in your theme's functions.php, not the search template. In the search template (and any other templates you want to paginate) you'd call the pagination with <?php paginate(); ?>
    – Chris Cox
    Commented Apr 27 at 9:11
  • I've updated my answer to make it a little clearer.
    – Chris Cox
    Commented Apr 27 at 10:49

Not the answer you're looking for? Browse other questions tagged or ask your own question.