WordPress SEO OG Tags with Hashbang (#!) URLs

If you load subpages of your WordPress site via AJAX you lose your Open Graph tags when you share those posts via Facebook or Twitter. Any social media site will pull the OG data from the main URL and ignores any hash URL.

If you share http://mysite.com/#my-post  Facebook will just use the base URL http://mysite.com/  to pull the metadata.

Test your current metadata

Use the following URLs to test your OG metadata and your twitter cards:

https://developers.facebook.com/tools/debug/og/object/
https://cards-dev.twitter.com/validator

Use hashbang instead of hash URLs

Change your AJAX links from http://mysite.com/#my-post to http://mysite.com/#!my-post

A hash URL is just a client instruction so you can’t react on this via PHP. If you use hashbang URLs instead the crawlers from Facebook and Twitter convert your request to /index.php?_escaped_fragment_=my-post so you can find the value in your $_SERVER global.

Redirect _escaped_fragment_ in .htaccess

We create a rewrite condition in our .htaccess to convert URLs with _escaped_fragment_ parameter to static URLs.

All my hashbang urls are used on the page /news/ so I add this to the rewrite rule:

RewriteCond %{QUERY_STRING} _escaped_fragment_=(.*)$
RewriteRule ^news/$ /%1/? [R=301,L]
  1. Original link: http://mysite.com/#!my-post
  2. Facebook crawler: http://mysite.com/?_escaped_fragment_=my-post
  3. Htaccess redirect: http://mysite.com/my-post/

Redirect real users back to the ajax page

Just in case a real user visits the crawler URL we redirect him back to the ajax version of the page. Add the following code to the top of the single.php  ( or single-posttype.php  for custom post types ):

if( 
    (
        empty($_SERVER['HTTP_X_REQUESTED_WITH']) 
        || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest'
    )
    && FALSE === stripos( $_SERVER['HTTP_USER_AGENT'], 'facebookexternalhit')
    && FALSE === stripos( $_SERVER['HTTP_USER_AGENT'], 'twitterbot')
    && FALSE === stripos( $_SERVER['HTTP_USER_AGENT'], 'bingbot')
    && FALSE === stripos( $_SERVER['HTTP_USER_AGENT'], 'googlebot')
    && FALSE === stripos( $_SERVER['HTTP_USER_AGENT'], 'bingbot')
    && FALSE === stripos( $_SERVER['HTTP_USER_AGENT'], 'yahoo') ) {
    wp_redirect( get_permalink( XXX ).'#!'.str_replace(home_url(), '', get_permalink($post->ID)), 302 );
    exit;
}

If the request is not an ajax request and the browser is not a bot redirect to the overview page and append the hashbang url.