Creating a Single Page Application (SPA) comes with many modern advantages to a traditional web site that can improve the user experience. These advantages include faster user experiences typically due to not having to reload the entire application in order to display changes in the content. This leads to an experience that can be very similar, or identical, to a user experience in a traditional desktop applicaiton. One area of  advantages that SPA do not tend to have is that web crawlers and search engines haven’t quite kept up with the ability to index and crawl Single Page Applications. Single Page Applications using AngularJS use a hash bang (ie #!) to take advantage of the browsers history. Thus, everything after the hash bang is not served to a web crawler or a web server (ie, Apache or Tomcat) and is only loaded by a browser or client.
Now, Google claims to be able to index SPA; however, it is my experience, that it has not been able to index three of my AngularJS web sites. Google Webmaster consistently has failed to crawl anything more than a few JavaScript files, which is not good for Search Engine Optimization (SEO). Even if you provide a sitemap.xml with all permutations of your dynamic pages listed, it will still fail to crawl your site in my experience. Also, for your sanity, you can check what your website looks like when Google crawls it by going to Google Webmaster/Search Console and under “Crawl” choose “Fetch as Google”, then “Fetch and Render” :
Then if it works, you can click the links in the table to see the render page from the Googlebot Web Crawler vs the User perspective. You will likely see that Google fails to correctly load your web site as the Googlebot.
If you notice that Google, or other search engines, are failing to crawl your site correctly, you do have options. When searching for how to do SEO on AngularJS and/or SPA, you will find many posts about having to pre-render all of your pages, as sort of a cache, to serve to search engines by doing some re-routing of the hash bang url path to a more traditional URL (ie, eliminate the signal to not load what comes after the hash bang). This can involve using services such as pretender.io or PhantomJS. These solutions have been used by many, but are too complicated in my opinion and they are not really acceptable for a technology like SPA, which is becoming the de facto standard for modern web applications. Luckily, we have a better option.
Instead of going through the chore of pre-rendering every single page in your web application, we can leverage HTML5. This is a two part solution. First, we will leverage some AngularJS code to automatically re-write hash bang URLs to a normal URL without the hash bang. For instance,
- http://www.wingspecials.today/#!/restaurants/detail/Archie’s-South-Side will become
- http://www.wingspecials.today/restaurants/detail/Archie’s-South-Side
This will result in a much more crawlable web site and can be done with minimal code changes. Here is the highlighted code change needed in your index.html
:
[code language=”html” highlight=”10″]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Wing Specials Today</title>
<!– This is needed for HTML5 Mode –>
<base href="/"><!– Make sure the base is above your stylesheet –>
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap-theme.css">
<link rel="stylesheet" href="css/wingnight.css">
. . .
</head>
. . .
[/code]
Note: This change must be above your stylesheet links!
Note: Locally, I test a few applications from the same root folder usingnpm start
. So, I need to change my base to<base href="/wingnight/">
for this to work locally.
Next, you will need to change your main module’s config.js
file. For instance:
[code language=”javascript” highlight=”5,21,22″]
(function( angular ) {
angular.module( ‘wingnight’ )
.config( wingnightConfig );
function wingnightConfig( $stateProvider, $urlRouterProvider, $locationProvider ) {
$urlRouterProvider.otherwise( ‘/restaurants/list’ );
$stateProvider
.state( ‘restaurants’, {
url : ‘/restaurants’,
template: ‘<restaurant-main></restaurant-main>’
} )
.state( ‘about-us’, {
url : ‘/about-us’,
template: ‘<about-us-main></about-us-main>’
} )
.state( ‘contact-us’, {
url : ‘/contact-us’,
template: ‘<contact-us-main></contact-us-main>’
} );
$locationProvider.html5Mode(true);
$locationProvider.hashPrefix(‘!’);
}
})( angular );
[/code]
Secondly, we need a solution that helps serves the pages when directly linked or when refreshing a page. Why? Well, when we refresh the page the web server is trying to load that given page; however, it doesn’t exist, because this is a Single Page Application and the routing links are dynamically created. This will result in an error like the following:
Cannot GET /wingnight/restaurants/detail/Bigham-Tavern-Mt.-Washington
Therefore, we need to be able to rewrite all URLs that are not at the root of the application to first load the root AngularJS application (ie, index.html), which has the references to your module(s). This would allow your Single Page Application to correctly load again from a browser refresh or via direct linking. Even better, all already establish links using the hash bang format should continue to work.
There are several options for getting your web server to redirect non-base links back to index.html
; however, the simplest one is an Apache .htaccess
file added to the root of your single page application.
[code language=”php”]
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ – [NC,L]
RewriteRule ^(.*) index.html [NC,L]
</IfModule>
[/code]
With this change, you will be able to refresh your page and have it work correctly. Also, you will be able to directly link to any of the dynamically created links generated by your SPA (ie, think sitemap.xml
). If you are experiencing issues still, please double check that you put your base href above your stylesheets!
Now, you should be able to verify using Google Search Console’s Fetch as Google with Render option, that Googlebot can indeed process your AngularJS 1.x Single Page Application. You can even request indexing to see what Googlebot crawls.
- Google Search Console Fetch As Google. Here we can see how to use Fetch as Google.
- Google Search Console Fetch As Google Render Failure. Here we can see that the Single Page Application was not correctly rendered on the left.
- Google Search Console Fetch As Google Render Success. Here we can see that the Single Page Application was correctly rendered on the left.
I have not checked in here for some time because I thought it was getting boring, but the last several posts are great quality so I guess I will add you back to my everyday bloglist. You deserve it my friend 🙂
I real lucky to find this internet site on bing, just what I was looking for : D besides saved to favorites.
Great post. I am facing a couple of these problems.
I would like to convey my affection for your generosity supporting those people who absolutely need guidance on this important issue. Your real dedication to getting the solution all-around appeared to be certainly useful and have specifically permitted guys just like me to achieve their dreams. Your valuable help and advice implies this much to me and further more to my mates. Regards; from all of us.
My wife and i were now more than happy Peter could finish up his survey from the precious recommendations he had from your very own site. It is now and again perplexing just to possibly be offering information that others might have been selling. And we remember we now have the blog owner to appreciate because of that. The type of illustrations you’ve made, the straightforward web site navigation, the friendships you aid to instill – it is mostly great, and it’s really letting our son and the family understand that issue is amusing, which is really indispensable. Many thanks for the whole lot!
I happen to be writing to let you know what a nice encounter my princess encountered using your web site. She figured out numerous details, which include how it is like to have a very effective giving spirit to make most people effortlessly master a number of tricky topics. You truly surpassed my expected results. Many thanks for displaying the interesting, trusted, edifying as well as easy tips on this topic to Gloria.
Hey! I simply would like to give a huge thumbs up for the great data you have got here on this post. I can be coming again to your blog for more soon.
What’s up, I want to subscribe for this weblog to get latest updates, therefore where can i do it please help.
Ahaa, its good conversation on the topic of
this paragraph here at this website, I have read all that, so now me also
commenting here.
Heya i am for the primary time here. I found this board and I find It really helpful & it helped me out much. I am hoping to present one thing back and aid others such as you aided me.
Its like you read my mind! You seem to know so much about this, like you wrote the book in it or something. I think that you can do with some pics to drive the message home a little bit, but instead of that, this is excellent blog. An excellent read. I will certainly be back.
Thank you a lot for providing individuals with remarkably breathtaking chance to read from this blog. It really is very pleasurable and also stuffed with fun for me and my office co-workers to visit your blog minimum thrice a week to read the fresh issues you will have. And of course, I’m so certainly contented considering the remarkable concepts you serve. Selected two areas in this posting are completely the best I have ever had.