ITHCWY: Robert Ellison's Blog

How to get SEO credit for Facebook Comments (the missing manual)

How to get SEO credit for Facebook Comments (the missing manual)

I've been using the Facebook Comments Box on this blog since I parted ways with Disqus. One issue with the Facebook system is that you won't get SEO credit for comments displayed in an iframe. They have an API to retrieve comments but the documentation is pretty light and so here are three critical tips to get it working.

The first thing to know is that comments can be nested. Once you've got a list of comments to enumerate through you need to check each comment to see if it has it's own list of comments and so on. This is pretty easy to handle.

The second thing is that the first page of JSON returned from the API is totally different from the other pages. This is crazy and can bite you if you don't test it thoroughly. For https://developers.facebook.com/docs/reference/plugins/comments/ the first page is https://graph.facebook.com/comments/?ids=https://developers.facebook.com/docs/reference/plugins/comments/. The second page is embedded at the bottom of the first page and is currently https://graph.facebook.com/10150360250580608/comments?limit=25&offset=25&__after_id=10150360250580608_28167854 (if that link is broken check the first page for a new one). The path to the comment list is "https://developers.facebook.com/docs/reference/plugins/comments/" -> "comments" -> "data" on the first page and just "data" on the second. So you need to handle both formats as well as the URL being included as the root object on the first page. Don't know why this would be the case, just need to handle it.

Last but not least you want to include the comments in a way that can be indexed by search engines but not visible to regular site visitors. I've found that including the SEO list in the tag does the trick, i.e.

<fb:comments href="..." width="630" num_posts="10">*Include SEO comment list here*</fb:comments>

I've included the source code for an ASP.NET user control below - this is the code I'm using on the blog. You can see an example of the output on any page with Facebook comments. The code uses Json.net.

FacebookComments.ascx:

<%@ Control Language="C#" AutoEventWireup="true" CodeFile="FacebookComments.ascx.cs" 
  Inherits="LocalControls_FacebookComments" %>

FacebookComments.ascx.cs

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Net;
using System.Text;
using System.Web;
using System.Web.Caching;
using Newtonsoft.Json.Linq;

// ReSharper disable CheckNamespace
public partial class LocalControls_FacebookComments : System.Web.UI.UserControl
// ReSharper restore CheckNamespace
{
    private const string CommentApiTemplate = "https://graph.facebook.com/comments/?ids={0}";
    private const string CacheTemplate = "localfacebookcomments_{0}";
    private const int CacheHours = 3;

    public string PostUrl { get; set; }

    protected void Page_Load(object sender, EventArgs e)
    {
        try
        {
            if (!string.IsNullOrWhiteSpace(PostUrl))
            {
                string cacheKey = string.Format(CultureInfo.InvariantCulture, 
                    CacheTemplate, PostUrl);

                if (HttpRuntime.Cache[cacheKey] == null)
                {
                    StringBuilder commentBuilder = new StringBuilder();

                    string url = string.Format(CultureInfo.InvariantCulture,
                                               CommentApiTemplate,
                                               PostUrl);

                    while (!string.IsNullOrWhiteSpace(url))
                    {
                        string json;
                        using (WebClient webClient = new WebClient())
                        {
                            json = webClient.DownloadString(url);
                        }

                        // parse comments
                        JObject o = JObject.Parse(json);
                        if ((o[PostUrl] != null) &&
                            (o[PostUrl]["comments"] != null) &&
                            (o[PostUrl]["comments"]["data"] != null))
                        {
                            // first page
                            AppendComments(o[PostUrl]["comments"]["data"], commentBuilder);
                        }
                        else if (o["data"] != null)
                        {
                            // other pages
                            AppendComments(o["data"], commentBuilder);
                        }
                        else
                        {
                            break;
                        }

                        // next page URL
                        if ((o[PostUrl] != null) &&
                            (o[PostUrl]["comments"] != null) &&
                            (o[PostUrl]["comments"]["paging"] != null) &&
                            (o[PostUrl]["comments"]["paging"]["next"] != null))
                        {
                            // on first page
                            url = (string) o[PostUrl]["comments"]["paging"]["next"];
                        }
                        else if ((o["paging"] != null) &&
                                 (o["paging"]["next"] != null))
                        {
                            // on subsequent pages
                            url = (string) o["paging"]["next"];
                        }
                        else
                        {
                            url = null;
                        }
                    }

                    string comments = commentBuilder.ToString();

                    HttpRuntime.Cache.Insert(cacheKey,
                        comments,
                        null,
                        DateTime.UtcNow.AddHours(CacheHours),
                        Cache.NoSlidingExpiration);

                    LiteralFacebookComments.Text = comments;
                }
                else
                {
                    LiteralFacebookComments.Text = (string)HttpRuntime.Cache[cacheKey];
                }
            }
        }
        catch (Exception)
        {
            LiteralFacebookComments.Text = string.Empty;
        }
    }

    private static void AppendComments(IEnumerable comments, 
        StringBuilder commentBuilder)
    {
        foreach (JObject comment in comments)
        {
            // write comment
            commentBuilder.AppendFormat(CultureInfo.InvariantCulture,
                                        "
{0} ({1})

\r\n"
,
                                        comment["message"],
                                        comment["from"]["name"]);

            // also write any nested comments
            if ((comment["comments"] != null) && (comment["comments"]["data"] != null))
            {
                AppendComments(comment["comments"]["data"], commentBuilder);
            }
        }
    }
}

Comments

Robert Ellison
How to get SEO value for Facebook Comments (aka wrestling with user-hostile JSON).
Nguyen van Phuc
good job
Pinky Chaurasia
Hi, That was nicely written. I really enjoyed your post. Thanks to share this nice post:) http://www.updateland.com/

Add Comment

All comments are moderated to weed out spam. Email address is optional and is only used to display your Gravatar.