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

Featured Image

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.

48
Top

Care to rate this post?

Author

Vanja Devcic

Frontend Developer

Vanja is Certified Frontend Developer, designer in soul and in job, who likes to use his skills to change the world through digital experience creating responsive, multi-languages web sites

Other posts from this author

Discussion 48 Comments

Add Comment
  1. Great work, but lines 28 and 29 of your code seem to be lacking the actual tags to output.

  2. Vanja Devcic

    @Johnboy

    Thanks for noticing. It is corrected!

  3. Hans Kuijpers

    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]

  4. 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

  5. Toni Anicic

    @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).

  6. Vanja Devcic

    @Johnboy,

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

  7. Hans Kuijpers

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

  8. Just tried it on Magento 1.7 (rc1) and it only returns a blank page. :(

  9. Drew Hunter

    Hi Vanja – I have created a small module for this – might be helpful

  10. Drew Hunter

    Link was cut off my last comment for some reason. Anyway, here it is: https://github.com/drewhunter/SeoPagination

  11. 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 ?

  12. Drew Hunter

    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.

  13. Vanja Devcic

    @Drew Hunter

    Thanks for the module!

  14. Hans Kuijpers

    @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

  15. Vanja Devcic

    @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.

  16. Hans Kuijpers

    @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.

  17. Toni Anicic

    Hans,

    Canonical is used to point to the original version of the content. So you can’t point all to page 1 since page 1 serves different content than other pages.

    Here is an article about basic stuff on how rel canonical works, there is a 20 minute video of Matt Cutts (Google’s engineer) explaining it embeded in the article as well – http://inchoo.net/online-marketing/how-to-use-rel-canonical-element-tag/

  18. Hans Kuijpers

    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.

  19. Toni Anicic

    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 :(

  20. Hans Kuijpers

    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

  21. Marcus

    @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

  22. Drew Hunter

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

    Thanks again,
    Drew

  23. Marcus

    @Drew Hunter: cool!

  24. Marcus

    @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

    ?

  25. Drew Hunter

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

  26. Marcus

    @Drew Hunter: absolutely perfect now!

  27. 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

  28. Ok, that was the method removeCanonical from your module which deleted my link rel=”start” tag.

    thank you !

  29. Thanks all for sharing your experience.
    @Drew Hunter: Thanks Drew for its module, it is working fine.

  30. Josua Marcel

    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’);

  31. 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())? ”:”; ?>

  32. 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() ?>

  33. 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.

  34. Tconn

    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?

  35. Tconn

    For anyone else struggling with this – i figured it out from a variation of the code posted here

    http://inchoo.net/online-marketing/how-do-relnext-and-relprev-work/

    This will only select the current products in the category to work with

  36. 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’);”

  37. 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

  38. James

    Thanks Drew. Works perf on 1.7

  39. 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.

  40. 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

  41. 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.

  42. 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 .

  43. 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?

  44. MudithaE

    Thanks for your solution. I’ve optimize it a bit.

    $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

  45. Wouter

    Thank you, works like a charm!

  46. Andres

    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.

  47. James

    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.

  48. how can javascript for rel=”prev” and next?
    thanks

Add Your Comment

Please wrap all source codes with [code][/code] tags.
Top