I can haz some Google love? A simple Drupal internal linking strategy.

I spent a little time today exploring some options for improving the internal linking on my blog. I'm not particularly worried about getting my site high up in the Google, but because I regularly add content it's a good sandbox for testing modules and techniques in achieving search engine friendliness.

A huge focus in SEO is getting higher ranking sites to link to yours, called 'incoming links', and the emphasis on this strategy leaves a lot of room for getting an edge using other techniques. In particular, 'internal links' - or links from one of your pages to another - is a technique way less exploited, but also much more under your control. Here's a couple of principles I've picked up over the years:

  • Links from one of your pages to another one is just as valid as a link from an external site. What matters is the PageRank of the page. Think "pages" not "sites"
  • The longevity of links comes into play, so rotating links aren't as effective in the long run as permanent ones
  • You can link to several pages from one page without diluting the PageRank that page passes onto the others, possibly up to a hundred

So, what this calls for is a strategy for easily creating permanent links from one page to another. Also, by linking to new pages from several others you increase the speed of getting indexed (in theory).

Here's what I done:

1. Installed the Related Content module

The Related Content module allows a user to create one-way associations between one node and several others. These associations can then shown in a variety of formats on the node page, or you can use an API to get a list and do your own formatting. To use it, you first have to set up a view and that's used to display a list of nodes to choose from. I thought the implementation was pretty witty, and I've got some ideas for improvements I'd like to see. For example, automated relationships based on keywords, 2-way associations, hiding the current node (so you don't associate the node with itself), supplying a default view, adding a few more formats, and ajax-loaded views so that exposed filters can display. Doesn't look like the module hasn't gotten much love in the last year or so, but it's functional and think it's a great start.

The role this module fills in terms of search engine friendliness is that it allows nodes to permanently link to multiple other nodes. For real users, it also gives them a next step on your pages. For pages with a high bounce rate, it lays out something that might be a bit enticing.

2. Installed the Vocabulary Index module

You can set up Views to create a list of taxonomy terms with links to the term pages, or you can install Vocabulary Index, which does that out of the box, and also includes a count of nodes for each term. This exposes links to vocabulary terms, which then provide internal links back to articles. I see this as another factor in a good internal linking strategy.

3. Installed the Clean Pagination module

Clean Pagination is a module I put together that will change links from /view-list?page=2 to /view-list/2. Google does a good job at indexing query string-ed pages, but it's generally thought that using clean URLs even in pagination is a stronger method. A setting in the module will also change your pagination links to include keyword-rich, more descriptive URLs in pagination links, which get replaced out with plain numbers when the page is loaded. I'm not sure if this is a good idea or not, but makes sense from an accessibility and search engine friendly perspective. Anyway, the option is there.

4. Used the monthly archive view

A monthly archive is an excellent way to generate permanent internal links. By using a monthly archive view (export found here) and adding it as a block, you expose the index pages for spidering. It took me a while to dig this view up, so I've included the code below for Views 2 in Drupal 6. Here's the view:

$view = new view;
$view->name = 'blog_archive';
$view->description = 'Display a list of months that link to content for that month.';
$view->tag = 'default';
$view->view_php = '';
$view->base_table = 'node';
$view->is_cacheable = FALSE;
$view->api_version = 2;
$view->disabled = FALSE; /* Edit this to true to make a default view disabled initially */
$handler = $view->new_display('default', 'Defaults', 'default');
$handler->override_option('sorts', array(
  'created' => array(
    'id' => 'created',
    'table' => 'node',
    'field' => 'created',
    'order' => 'DESC',
    'granularity' => 'second',
    'relationship' => 'none',
  ),
));
$handler->override_option('arguments', array(
  'created_year_month' => array(
    'id' => 'created_year_month',
    'table' => 'node',
    'field' => 'created_year_month',
    'default_action' => 'summary asc',
    'style_plugin' => 'default_summary',
    'style_options' => array(
      'count' => 1,
      'override' => 1,
      'items_per_page' => '30',
    ),
    'wildcard' => 'all',
    'wildcard_substitution' => 'All',
    'title' => '%1',
    'relationship' => 'none',
    'validate_type' => 'none',
    'validate_fail' => 'not found',
    'default_argument_type' => 'fixed',
  ),
));
$handler->override_option('filters', array(
  'status' => array(
    'id' => 'status',
    'table' => 'node',
    'field' => 'status',
    'operator' => '=',
    'value' => 1,
    'group' => 0,
    'exposed' => FALSE,
    'expose' => array(
      'operator' => FALSE,
      'label' => '',
    ),
    'relationship' => 'none',
  ),
  'type' => array(
    'operator' => 'in',
    'value' => array(
      'blog' => 'blog',
    ),
    'group' => '0',
    'exposed' => FALSE,
    'expose' => array(
      'operator' => FALSE,
      'label' => '',
    ),
    'id' => 'type',
    'table' => 'node',
    'field' => 'type',
    'relationship' => 'none',
  ),
));
$handler->override_option('access', array(
  'type' => 'none',
  'role' => array(),
  'perm' => '',
));
$handler->override_option('title', 'blog archive');
$handler->override_option('items_per_page', 30);
$handler->override_option('use_pager', '1');
$handler->override_option('row_plugin', 'node');
$handler->override_option('row_options', array(
  'teaser' => TRUE,
  'links' => TRUE,
));
$handler = $view->new_display('page', 'Page', 'page');
$handler->override_option('path', 'drupal-blog-archive');
$handler->override_option('menu', array(
  'type' => 'none',
  'title' => '',
  'description' => '',
  'weight' => 0,
  'name' => 'navigation',
));
$handler->override_option('tab_options', array(
  'type' => 'none',
  'title' => '',
  'description' => '',
  'weight' => 0,
));
$handler = $view->new_display('block', 'Block', 'block');
$handler->override_option('arguments', array(
  'created_year_month' => array(
    'id' => 'created_year_month',
    'table' => 'node',
    'field' => 'created_year_month',
    'default_action' => 'summary asc',
    'style_plugin' => 'default_summary',
    'style_options' => array(
      'count' => 1,
      'override' => 0,
      'items_per_page' => '30',
    ),
    'wildcard' => 'all',
    'wildcard_substitution' => 'All',
    'title' => '%1',
    'relationship' => 'none',
    'validate_type' => 'none',
    'validate_fail' => 'not found',
    'default_argument_type' => 'fixed',
  ),
));
$handler->override_option('block_description', 'Archive list');
$handler->override_option('block_caching', -1);



Comments

Thanks that was useful on a number of levels

Nice, I'm glad to help!

Thanks for the insight. I look forward to applying these techniques to my own websites.

My pleasure, good luck!

Very eager to learn more about SEO strategies as they relate to Drupal. Thanks for this post!

Great post, thank you.

That pagination module looks very useful.

Do you know if the related content module is at all similar to the 'similar module: http://drupal.org/project/similar?

Any advantages to the latter?

Looks like an interesting module, I'll have to experiment with it. It looks like Similar Entries auto-generates a list of related content, which is handy. This approach is good for low work overhead, but most likely doesn't take into account the link permanence issue. Google knows the lifespan of a link, and the longer it is the more weight it gets. Besides that, I definitely like the idea.