{"id":27082,"date":"2015-10-05T11:02:09","date_gmt":"2015-10-05T05:32:09","guid":{"rendered":"http:\/\/www.tothenew.com\/blog\/?p=27082"},"modified":"2020-01-08T16:56:35","modified_gmt":"2020-01-08T11:26:35","slug":"angular-seo","status":"publish","type":"post","link":"https:\/\/www.tothenew.com\/blog\/angular-seo\/","title":{"rendered":"Angular + SEO"},"content":{"rendered":"<p>There&#8217;s a phase in every project when development reaches its completion and next step is to make it search engine friendly.<\/p>\n<p>When I had to do this task in my project, at that time I was a little bit confident that this is a very simple task and we just need to set meta tags and some other html tags so that google can crawl easily. But while setting these tags with the <a title=\"Building Intuitive Frontend Interfaces with AngularJS\" href=\"http:\/\/www.tothenew.com\/blog\/building-intuitive-frontend-interfaces-with-angularjs\/\">help of angular<\/a>, what we noticed was, crawled pages are not parsed \/rendered and have angular expressions ({{}}) for meta tags.<\/p>\n<p>Crawlers (or bots) are designed to crawl HTML content of web pages but due to AJAX operations for asynchronous data fetching, this became a problem as it takes sometime to render page and show dynamic content on it. Similarly, <a title=\"AngularJS development company\" href=\"http:\/\/www.tothenew.com\/front-end-angularjs-development\">AngularJS<\/a> also use asynchronous model, <a href=\"http:\/\/www.tothenew.com\/blog\/optimization-of-angularjs-single-page-applications-for-web-crawlers\/\">which creates a problem for Google crawlers<\/a>.<\/p>\n<p>Google looks for <strong>#!<\/strong> in our site urls and then takes everything after the #! and adds it in <strong>_escaped_fragment_<\/strong> query parameter. Some developers create basic html pages with real data and serve these pages from server side at the time of crawling. So we thought that, why not we render same pages with <strong>PhantomJS<\/strong> on serve side which has<strong> _escaped_fragment_<\/strong>.<\/p>\n<p>There are few steps which we followed in our application to make it crawlable.<\/p>\n<p><strong>Client Side : &#8211;<\/strong><\/p>\n<p>1. Adding meta tag to the head of your page.<\/p>\n<p>[js]<\/p>\n<p>&lt;meta name=&quot;fragment&quot; content=&quot;!&quot;&gt;<\/p>\n<p>[\/js]<\/p>\n<p>This helps search engine crawlers that this page has dynamic JavaScript content that needs to be crawled.<\/p>\n<p>2. If you use hash urls (#), change them to the hash-bang (#!) by \u00a0adding<\/p>\n<p>[js]<\/p>\n<p>$locationProvider.hashPrefix(&#8216;!&#8217;)<\/p>\n<p>[\/js]<\/p>\n<p>in your app.js.\u00a0You would not see any diffrence if you are using html5Mode.<\/p>\n<p>3. As each page may have different seo data so you can create a service or a util method to set <a title=\"SEO in 2020\" href=\"https:\/\/www.tothenew.com\/blog\/seo-in-2020-trends-predictions-tips-strategy\/\">SEO data<\/a> . I would prefer to use <strong>$rootScope<\/strong> to store <strong>SEO<\/strong>\u00a0data object so that it will be used in any controller and easily\u00a0bind in <strong>index.html<\/strong> page.<\/p>\n<p><strong>Util Service : &#8211;<\/strong><\/p>\n<p>[js]<\/p>\n<p>(function (window, angular) {<\/p>\n<p>angular.module(&#8216;myApp&#8217;).factory(&#8216;UtilService&#8217;, [&#8216;$rootScope&#8217;,\u00a0function ($rootScope) {<\/p>\n<p>\tvar seoInformation = function (seoData) {<\/p>\n<p>\t\t$rootScope.seoInformation = {};<\/p>\n<p>\t\tif (seoData) {<\/p>\n<p>\t\t\t$rootScope.seoInformation.pageTitle = seoData.pageTitle;<\/p>\n<p>\t\t\t$rootScope.seoInformation.metaKeywords = seoData.metaKeywords;<\/p>\n<p>\t\t\t$rootScope.seoInformation.metaDescription = seoData.metaDescription;<\/p>\n<p>\t\t}<\/p>\n<p>\t};<\/p>\n<p>\treturn {<\/p>\n<p>\t\tseoInformation: seoInformation<\/p>\n<p>\t};<\/p>\n<p>}]);<\/p>\n<p>})(window, angular);<\/p>\n<p>[\/js]<\/p>\n<p><strong>My Controller :-<\/strong><\/p>\n<p>[js]<\/p>\n<p>(function (jquery, angular) {<\/p>\n<p>angular.module(&#8216;myApp&#8217;).controller(&#8216;MyCtrl&#8217;, [&#8216;$state&#8217;, &#8216;UtilService&#8217;, function ($state, UtilService) {<\/p>\n<p>\tvar _that = this;<\/p>\n<p>\tvar _setSEOInformation = function() {<\/p>\n<p>\t\tvar seoInformation = {<\/p>\n<p>\t\t\tpageTitle : &#8216;My title&#8217;,<\/p>\n<p>\t\t\tmetaKeywords : &#8216;My meta keywords&#8217;,<\/p>\n<p>\t\t\tmetaDescription : &#8216;My meta Description&#8217;<\/p>\n<p>\t\t};<\/p>\n<p>\t\tUtilService.seoInformation(seoInformation);<\/p>\n<p>\t};<\/p>\n<p>\t_that.init = function () {<\/p>\n<p>\t\t\/\/ Set Seo Information<\/p>\n<p>\t\t_setSEOInformation();<\/p>\n<p>\t};<\/p>\n<p>}]);<\/p>\n<p>})(jQuery, angular);<\/p>\n<p>[\/js]<\/p>\n<p><strong>Server side : &#8211;<\/strong><\/p>\n<p>Because we serve our angular app using node server, we use <strong>node-phantom<\/strong> module to serve crawle pages by writting a middleware.<\/p>\n<p>1. Require node-phantom module in you file.<\/p>\n<p>[js]<\/p>\n<p>var phantom = require(&#8216;node-phantom&#8217;);<\/p>\n<p>[\/js]<\/p>\n<p>2. Write a middleware for any request and serve accordinglly.<\/p>\n<p>[js]<\/p>\n<p>app.use(function (request, response, next) {<\/p>\n<p>\tvar pageUrl = request.query[&quot;_escaped_fragment_&quot;];<\/p>\n<p>\tif (pageUrl !== undefined) {<\/p>\n<p>\t\tphantom.create(function (err, ph) {<\/p>\n<p>\t\treturn ph.createPage(function (err, page) {<\/p>\n<p>\t\t\tvar fullUrl = request.protocol + &#8216;:\/\/&#8217; + request.get(&#8216;host&#8217;) + pageUrl;<\/p>\n<p>\t\t\treturn page.open(fullUrl, function (err, status) {<\/p>\n<p>\t\t\tpage.get(&#8216;content&#8217;, function (err, html) {<\/p>\n<p>\t\t\t\tresponse.statusCode = 200;<\/p>\n<p>\t\t\t\tresponse.end(html);<\/p>\n<p>\t\t\t\tph.exit();<\/p>\n<p>\t\t\t});<\/p>\n<p>\t});<\/p>\n<p>});<\/p>\n<p>});<\/p>\n<p>} else {<\/p>\n<p>\tnext();<\/p>\n<p>}<\/p>\n<p>});<\/p>\n<p>[\/js]<\/p>\n<p>&nbsp;<\/p>\n<p>In this way we can make our webpages crawlable in Angular js. Hope this will help you!!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>There&#8217;s a phase in every project when development reaches its completion and next step is to make it search engine friendly. When I had to do this task in my project, at that time I was a little bit confident that this is a very simple task and we just need to set meta tags [&hellip;]<\/p>\n","protected":false},"author":146,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":4},"categories":[1439,3429,1185],"tags":[3955,1186,955,4697,1226,2409,55,2411,404],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/27082"}],"collection":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/users\/146"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/comments?post=27082"}],"version-history":[{"count":1,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/27082\/revisions"}],"predecessor-version":[{"id":53408,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/posts\/27082\/revisions\/53408"}],"wp:attachment":[{"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/media?parent=27082"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/categories?post=27082"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tothenew.com\/blog\/wp-json\/wp\/v2\/tags?post=27082"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}