How to implement rel=prev and rel=next to Magento’s pagination?

How to implement rel=prev and rel=next to Magento’s pagination?

As most of you know that “When dealing with online stores with a lot of products, pagination on category pages can get really problematic for search engines” like Toni Anicic wrote in his article. I don’t want to repeat his words, but to show you how you can add rel=”prev” and rel=”next” link tag attributes in the head tag for pages, which will boost your SEO. This peace of code is already provided by Magento community, but this is improved version.

Tested in Magento CE 1.6.1.0.

Implementation

1. So, if you didn’t already modified head.phtml file, create identical directory hierarchy and copy/paste head.phtml in your theme or package.

Path example if using package:
..\app\design\frontend\[your_package_name]\default\template\page\html\head.phtml

Path example if using theme:
..\app\design\frontend\default\[your_theme_name]\template\page\html\head.phtml

2. Add code below to head.phtml. I’ve added code at the bottom of file.

<?php
$actionName = $this->getAction()->getFullActionName();
if ($actionName == 'catalog_category_view') // Category Page
{
$category = Mage::registry('current_category');
$prodCol = $category->getProductCollection()->addAttributeToFilter('status', 1)->addAttributeToFilter('visibility', array('in' => array(Mage_Catalog_Model_Product_Visibility::VISIBILITY_IN_CATALOG, Mage_Catalog_Model_Product_Visibility::VISIBILITY_BOTH)));
$tool = $this->getLayout()->createBlock('page/html_pager')->setLimit($this->getLayout()->createBlock('catalog/product_list_toolbar')->getLimit())->setCollection($prodCol);
$linkPrev = false;
$linkNext = false;
if ($tool->getCollection()->getSelectCountSql()) {
if ($tool->getLastPageNum() > 1) {
if (!$tool->isFirstPage()) {
$linkPrev = true;
if ($tool->getCurrentPage() == 2) {
$url = explode('?', $tool->getPreviousPageUrl());
$prevUrl = @$url[0];
}
else {
$prevUrl = $tool->getPreviousPageUrl();
}
}
if (!$tool->isLastPage()) {
$linkNext = true;
$nextUrl = $tool->getNextPageUrl();
}
}
}
if ($linkPrev) echo '<link rel="prev" href="' . $prevUrl . '" />';
if ($linkNext) echo '<link rel="next" href="' . $nextUrl . '" />';
}
 
?>

Result

Below is a result if you are on page 3.

<head>
...
<link rel="prev" href="http://www.example.com/store.html?p=2">
 
<link rel="next" href="http://www.example.com/store.html?p=4">
...
</head>

Search engine optimization in Magento’s Configuration

After implementing rel=”prev” and rel=”next” you need to re-config Magento’s SEO options, which means that you don’t need anymore Canonical Link Meta Tag For Categories. Below is a example how we setup Magento’s SEO options for one of our clients.

Hope this text will help you. And if you are migrating from Magento 1 to Magento 2 there is a handy SEO checklist that will also help you.

You made it all the way down here so you must have enjoyed this post! You may also like:

Build your 2017 Magento SEO strategy with these tips Ivona Namjesnik
Ivona Namjesnik, | 10

Build your 2017 Magento SEO strategy with these tips

Free eCommerce SEO consultations by Inchoo at Meet Magento Poland Tea Pisac Benes
Tea Pisac Benes, | 0

Free eCommerce SEO consultations by Inchoo at Meet Magento Poland

In SERP title tags no longer control what’s shown Toni Anicic
Toni Anicic, | 10

In SERP title tags no longer control what’s shown

60 comments

  1. Please be careful when using this code, as it causes a security vulnerability if you give certain parameters via GET.
    For example:

    ?limit=24"><img src%3da onerror%3dprompt(/XSS/)>

    Please escape the $linkNext variable for example like:

    if ($linkNext) echo '<link rel="next" href="' . htmlspecialchars($nextUrl, ENT_QUOTES, 'UTF-8') . '" />'; 
  2. I would’ve excepted more from a “Professional Magento Solutions Partner”.
    Coding Standards exists for a reason and this code example doesn’t belong into template files.

    1. Besides the fact it doesn’t belong in templates, it’s most likely the worse implementation one could do. Besides the fact that it does’t even take in consideration if there active filters, there are better ways to do it WITHOUT doing an extra unnecessary sql query and collection hydration.

  3. great code but dont work for category filters

    Prefer this solution to remove p=1 (to not remove filters):
    if ($tool->getCurrentPage() == 2) {
    $replace = array(‘?p=1’, ‘&p=1’);
    $urlData[‘prev’] = str_replace($replace, ”, $tool->getPreviousPageUrl());
    }

    Now searching to have the correct last page number with filters, must not be very difficult.

  4. whats the best way to implament rel = “next/prev” if we have filters?
    the filtered view results in paginated content.
    the filtered view has different urls: exmple: https://modli.co/dresses.html?category=45&price=13%2C71&size=25
    right now we have canonical tag all to the main category page.

    look at what it says in searchengineland: http://searchengineland.com/implementing-pagination-attributes-correctly-for-google-114970
    look at Advanced Techniques paragraph. do you agree? it seem like google will index the page multiple times.

    what would you recommend to do in this case?

  5. I’ve added this to head.phtml but nothing happens. Is this a cache issue or are you supposed to add this to the template phtml that was created when you set up the block tag on the product page?

  6. Hello,
    This code is working properly except when we filter product on category page. Can you please help me to solve this problem?

    Thanks,

  7. Great piece of code. Thank you.

    Double page titles is another SEO matter. With your code we can add and get rid of the SEO tools warnings about double page titles once and for all.

    $this->setTitle($this->getTitle() . ” – Page ” . $tool->getCurrentPage());

    Last but not least, we can add our active filters on the page title too.
    $category = Mage::registry(‘current_category’);
    $filters = Mage::getSingleton(‘catalog/layer’)->getState()->getFilters();

    if(count($filters) >=1 )
    foreach($filters as $f) {
    $this->setTitle($this->getTitle() . ” – ” . $f->getLabel());
    }

    What do you think?

  8. Hi guys, @Toni Acic, @Drew Hunter @Hans Kuijpers,

    As you’ve already noted, Magento point to incorrect canonical version (first page) when we talk about category pages.

    I’d like to ask if any of you could provide a example how can I fix this behavior by Magento, to be specific, I’d like category pages canonical point to it’s respective page number (page), not to the first page of the serries.

    Thank you very much guys!

    P.S. Drew, thanks for the module, works really great for me.

  9. I have a similar problem to Andres, but to me it looks like Magento is not counting correctly.

    In a category with 4 pages and 91 products (30 products per page default view), the “getlastpagenum” returns 6.

    On other categories with one page that value is 1, and on another it is 2.

    Could magento be doing counting disabled/unavailable SKUs in the math for this function?

    thanks.

  10. Nice tutorial, I only got a problem, when I’m in the last page, It renders the rel=”next”, how can I disable this?..Thanks.

  11. Thanks for your solution. I’ve optimize it a bit.
    [code][/code]
    $toolbar = $this->getToolbarBlock()->setCollection($_productCollection);
    $pager = Mage::getBlockSingleton(‘page/html_pager’)->setLimit($toolbar->getLimit())->setCollection($_productCollection);

    if ($toolbar->getCollection()->getSize() > 0):?>
    isFirstPage()): ?>
    <a class="prev-page" href="getPreviousPageUrl() ?>” title=”__(‘Previous’) ?>”>Previous

     PreviousDisable

    Page getCurrentPage(); ?> of getLastPageNum(); ?>

    isLastPage()): ?>
    <a class="next-page" href="getNextPageUrl() ?>” title=”__(‘Next’) ?>”> Next

     NextDisable

    [code][/code]

  12. Yes, but if you disable rel canonical as you described, then this may cause Mass dup content issues due to URL rewrite.

    Currently, I have set URL rewrite to SEF URLs and I have canonical tags set to each page. In some cases though paginated content appears with rel canonical to each page, i.e. ?page=2 has rel canonical to ?page=2 which is not good…

    But still it is better than handle the paginated content and have x2 of all of your pages due to URL rewrite, isn’t it?

  13. Hello,
    Thanks this helpful tutorial.
    I have a question if you can help me please :
    How to rewrite pagination url (for exemple ?p=2) in htaccess in magento.
    Thanks in advance for your answer .

  14. THANKS THANKS THANKS, just copied-pasted and worked!!!!

    Works like a charm in 1.4 BUT there’s is one thing to improve: Magento still will create a /your-search-page?p=1 as a duplicate of /your-search-page.

    I’ve solved this by tweaking the same file head.phtml on the line for robots :

    //Old line:
    //<meta name="robots" content="<?php echo htmlspecialchars($this->getRobots()) ?>" />
    //Substituted by the following code:
    
    $indexing = true;
    $url=$_SERVER['REQUEST_URI'];
    if(substr_count($url, "?p=1") > 0 )
    	$indexing = false;
    
    <?php
    if($indexing ==true)
    {
     //Use of the option set in Magento admin
    ?>
    <meta name="robots" content="<?php echo htmlspecialchars($this->getRobots()) ?>" />
    <?php 
    }else{
    ?>	
    <meta name="robots" content="NOINDEX,NOFOLLOW" />	
    <?php 
    }
    ?>

    I use this method to eliminate pages I don’t want to be indexed (including parameter like “dir,mode,price…”), but I suppose this is a different topic.

  15. Ups, I tried to paste the code into the textarea but I guess the ” < ? " are not welcome… sorry, if the moderator considers my solution could be interesting don't hesitate contact me and I'll post it back.

    Regards

  16. THANKS THANKS THANKS, just copied-pasted and worked!!!!

    Works like a charm in 1.4 BUT there’s is one thing to improve: Magento still will create a /your-search-page?p=1 as a duplicate of /your-search-page.

    I’ve solved this by tweaking the same file head.phtml on the line for robots :
    0 )
    $indexing = false;

    if($indexing ==true)
    {
    //We use the option set in Magento admin
    ?>
    <meta name="robots" content="getRobots()) ?>” />

    I use this method to eliminate pages I don’t want to be indexed (including parameter like “dir,mode,price…”), but I suppose this is a different topic.

  17. Hi,
    First of all I would like to thank all of you for sharing your expertise.
    I have been reading this post since I faced the same problem with link rel and canonical in conjuntion, as Toni well described… I have not been able to solve it yet. Has any of you finally solved it?
    Thanks you all

  18. Thanks for the guide and the module. I also edited app\code\core\Mage\Catalog\Block\Category\View.php to make the canonical tag link to the view all page instead of the first page of a category. Search for ” $headBlock->addLinkRel(‘canonical’, $category->getUrl());” within that file and replace it with ” $headBlock->addLinkRel(‘canonical’, $category->getUrl().’?limit=all’);”

  19. I have implemented this fine, apart from product page count and $tool->getLastPageNum() dont match

    I narrowed it down to $prodCol, which is outputting the total amount of products in the category. That can be seem in the admin, not the visible products on the front end

    Is there any way to get this to match the actual products in the category – I dont think it is taking the whether the products are in stock, so it doesnt know which is the last page to not have the “next tag” on

    Can anyone help please?

  20. In my oppinon. the best solution ist to use the new rel=pref and rel=next und set canonical fpr categories to no.

    The problem is, we have many many paremeters to sort the products, so we still need the canonical tag. (WMT parameter settings did not work – actually we got over 70.000 pages in teh google index – but we only have about 2.500 “real” pages. Is there a better canonical extension then the original magento one? Because like you guys said before, the magento canonical isn’t perfect it points page 20 to page 1.

  21. Sorry the code was not from magento core code. Here is a better sample:

    canShowFirst()): ?>
    <a rel="prev" class="first" href="getFirstPageUrl() ?>”>1

    isLastPage()): ?>

    <a rel="next" class="nextgetAnchorTextForNext()): ?> i-next” href=”getNextPageUrl() ?>” title=”__(‘Next’) ?>”>
    getAnchorTextForNext()): ?>
    <img src="getSkinUrl(‘images/pager_arrow_right.gif’) ?>” alt=”__(‘Next’) ?>” class=”v-middle” />

    getAnchorTextForNext() ?>

  22. Why you don’t instead copy the app/design/frontend/base/default/template/pager.phtml file and put it in your theme and set in the “a” tag the attribute “rel=prev” or “rel=next” in the correct link?!!!! It’s a cleaner solution instead of make your complicated code and work for all pages using the pager navigation.

    Below a sample of the pager.phtml
    isFirstPage()){ ?>
    <a rel="prev" class="previousgetAnchorTextForPrevious()): ?> i-previous” href=”javascript:ajaxLoad(‘getPreviousPageUrl() ?>’);” title=”__(‘Previous’) ?>”>
    <img src="getSkinUrl(‘images/i_pager-prev.gif’) ?>” alt=”__(‘Previous’) ?>” class=”v-middle” />getAnchorTextForPrevious()): echo $this->getAnchorTextForPrevious(); endif;*/ echo (!$this->isFirstPage())? ”:”; ?>

    ….

    isLastPage()){ ?>
    <a rel="next" class="next" href="javascript:ajaxLoad(‘getNextPageUrl() ?>’);” title=”__(‘Next’) ?>”>
    getAnchorTextForNext()): echo $this->getAnchorTextForNext(); endif;*/ ?><img src="getSkinUrl(‘images/i_pager-next.gif’) ?>” alt=”__(‘Next’) ?>” class=”v-middle” />isLastPage())? ”:”; ?>

  23. Parse error: syntax error, unexpected T_VARIABLE in …/template/page/html/head.phtml on line 32

    this is my 32 line :
    $category = Mage::registry(‘current_category’);

  24. Hi Drew,

    I have one question for you.

    I wanted to add the rel start to your module.
    Like this way :

    		if (!$pager->isFirstPage() && $numPages > 1) {
    			$headBlock->addItem('link_rel', $pager->getPagerUrl(array($pager->getPageVarName() => NULL)), 'rel="start" rev="child" type="text/html"');
    		}

    But unfortunatly the method getPagerUrl return nothing.
    That is a bit strange because I used this method elsewhere and it works perfectly.

    I tried also Mage::registry(‘current_category’)->getUrl() but it return nothing neither…

    However, Mage::registry(‘current_category’) seems filled when you call the event observer.

    Have you some advice ?

    Cheers,
    Ilan

  25. Hi Marcus – grab the latest source code. There was a missing method call – all should now be working correctly.

  26. @Drew Hunter: I verrided the old files and flushed the cache. But then I get the error: Call to a member function getLimit() on a non-object in …/app/code/local/Dh/SeoPagination/Model/Paginator.php on line 39

    ?

  27. Hey Marcus – thanks for spotting that. I have fixed the bug and updated the module.

    Thanks again,
    Drew

  28. @Drew Hunter: your module works perfect but if I enable the “All” option in the “Show X Per Page” dropdown and choose “All” in frontend it only shows me one product even when I have e.g. 400 products in this category. If I disable your module it works like it should with the “All” option. Is it a bug?

    Thanks for your answer in advance,
    Marcus

  29. Found another Google source to empower your way of working.

    http://youtu.be/6AmRg3p79pM?t=3m34s

    “Avoid rel=’canonical’ for each component page in a series to page one.
    Instead, use new rel=’next’ and rel=’prev’ markup for paginated content” – Maile Ohye from Google

  30. Hans,

    No…

    Ascending and descending can be canonicalized if the only difference is the sort order. The problem is, when you have paginated content, ascending and descending sort order is no longer canonical version, it’s different content, not just sort order.

    I’m not sure how to explain it clearer 🙁

  31. viewing the video now
    I think my question is answered on 13:58
    Q: Do the page have to be bit-for-bit identical?
    A: No, but they should be similar. Slight differences are okay.

    “…merging ascending sorted page with descending sorted page using canonical URL is no problem. The content is similar” -> Matt Cutts

    Canonical is a referral to the preferred version. My preferred version is the first page of a multi page catalog, not indexing the others. For me the product detail pages are allowed to be indexed as well as the first page of a catalog.

  32. @Vanja
    Should it? Than I have to rewrite my definition of canonical URL. I always refer the canonical URL to the first page without any parameters.

    Cut off all parameters and refer to the first page of the category shown.

    Please correct me if I’m wrong. Be so kind to refer to some documentation where it states how a Canonical Url should look like. I’m lost now.

  33. @Hans

    Magento Canonical is not working right. It should display <link href=”http://threetosties.nl/apparel?p=2″ rel=”canonical”> not <link href=”http://threetosties.nl/apparel” rel=”canonical”>

    But if you could put canonical link to “View All”, then it will catch all products as unique from it.

  34. @Drew
    Thx for the module…. great!

    @Toni
    I was aware that the Magento implementation of Canonical URL did not work well… So I’m using Yoast Canonical URL extension for a while.
    Just disabled the extension (set active state to false in app/etc/modules/Yoast_CanonicalUrl.xml) and tried again. I came to the conclusion that Magento fixed the Canonical URL. Can anyone confirm this?

    please check the source of http://threetosties.nl/apparel?p=5

  35. Hi Ilan – from my understanding, query parameters need to be kept consistent throughout the any series of links where rel=”next” and rel=”prev” are being used. So the module will keep any query parameters intact for filtered pages etc.

    If you are worried about duplicate content, then that is precisely what canonical links are for – and so you should be using these too.

  36. Hi Drew,

    One question regarding your module.

    Why all urls include parameters like attribute param, list, order, dir, price ???

    We lost all benefit due to it ?

  37. @Toni -> True. That’s why I use some custom development just like Vanja points out.

  38. @Johnboy,

    That’s correct. But require custom development because default Magento’s functionality doesn’t work that way.

  39. @Hans,

    That canonical is not implemented well in Magento’s categories by default. It points page 2 to page 1, page 3 to page 1… and so on. So disabling it would be a smart choice (canonical should only point to same content pages, and page 2 has different content than page 1).

  40. As Hans said canonical doesn’t need to be disabled, the ideal situation is that the canonical actually includes the page parameter in it’s URL

  41. nice… but too bad it’s a template override and not an extension though.
    Failed creating an extension myself. The link rel prev and next keeps being overwritten by canonical. 🙂

    With the implementation of rel next and prev you don’t have to disable the canonical URL. At least not regarding the article on the Google Webmaster Blog. http://googlewebmastercentral.blogspot.com/2011/09/pagination-with-relnext-and-relprev.html
    [quote]rel=”next” and rel=”previous” on the one hand and rel=”canonical” on the other constitute independent concepts. Both declarations can be included in the same page. [/quote]

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <blockquote cite=""> <code> <del datetime=""> <em> <s> <strike> <strong>. You may use following syntax for source code: <pre><code>$current = "Inchoo";</code></pre>.

Tell us about your project

Drop us a line. We'd love to know more about your project.