© Elad Elrom 2016

Elad Elrom, Pro MEAN Stack Development, 10.1007/978-1-4842-2044-3_8

8. AngularJS SEO

Elad Elrom

(1)New York, USA

In previous chapters, we covered the process of building an AngularJS application from front-end to back-end services. It’s easy to see why AngularJS is popular, with its millions of available libraries, ease of use, and ability to build code that can be deployed on any device. However, many developers and companies still refuse to use AngularJS due to a lack of understanding, SEO and thought that AngularJS can not be crawled by search engines the same as other HTML static pages. In this chapter, we will walk you through the steps of how to set your site to be SEO friendly, so that the robots can crawl and display the pages as if you built a purely HTML site. Sure, Google has updated their crawler and can now execute JavaScript, but other search crawlers, as well as social media web sites, still crawl pages after being rendered by JavaScript, so you will still need to create a static HTML page for the best SEO results.

Config AngularJS Redirect Settings

The redirect happens in your AngularJS apps when using the hashbang “#!” tag in your route. This is a common practice; you can see large sites such as Twitter doing the same.

Using Twitter as an example, pick a user and add the “hashbang” tag:

http://twitter.com/#!/EliScripts

This will redirect you to http://twitter.com/EliScripts .

Start a New AngularJS Seed Project

To get started, open WebStorm and create a new “angular-seed” project.

As you may recall:

  1. “Create New Project” ➤ “AngularJS”

  2. Call the project “SEOTester” and click “Create.”

  3. Open the WebStorm Terminal window (bottom left corner) and run these two commands: “npm install” and “npm start”.

  4. Open a separate terminal by clicking the plus sign ("New Session") and type the following command: “open http://localhost:8000”.

That’s it. You should see the same page with the tabs we created in Chapter 5 (Figure 8-1).

A416092_1_En_8_Fig1_HTML.jpg
Figure 8-1. “angular-seed” landing page: index.html

Now that we have a seed project, we will be following this process:

  1. Change the AngularJS project to use HTML5 routing mode.

  2. Create a snapshot of your pages.

  3. Redirect the pages to the static snapshot.

  4. Submit the pages to search engines.

AngularJS HTML Mode and Hashbang

AngularJS offers Hashbang and HTML5 routing modes.

Out of the box, AngularJS seed projects use Hashbang mode. You can try it yourself by redirecting to the following URL: http://localhost:8000/#!/view1

The first step is to update how AngularJS routes pages so that it redirects a page without the hashbang. The URL will look like this: localhost:8000/view1 .

To do so:

Open SEOTester/app/app.js and you will see the $locationProvider with the hashPrefix('!') attached:

config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) {                  
  $locationProvider.hashPrefix('!');


  $routeProvider.otherwise({redirectTo: '/view1'});
}]);

The $location service is the API used by AngularJS to redirect our app based on the URL. $location API can be used to watch URL changes, history links, and back and forward buttons. It is similar to the “window.location” in JavaScript, but with some extra capabilities for HTML5. You can learn more about the $location API in AngularJS docs: https://docs.angularjs.org/guide/$location

Looking at the app.js, the code looks for a Hashbang tag and redirects accordingly—otherwise it would use the default view: “view1.” You can confirm this by typing the root URL, http://localhost:8000/, which redirects to view1: http://localhost:8000/#!/view1.

We want to update the URL and remove the Hashbang for a more standard URL (HTML5 mode). The first step is to set your app.js for HTML5 mode. It’s better to use a URL that includes a description of the product or article than just an item ID. Google search engine Page Rank (PR) formula published that they give priority to sites with a URL that hits the keywords you will want to promote on your page.

In fact, according to Google, “Some users might link to your page using the URL of that page as the anchor text. If your URL contains relevant words, this provides users and search engines with more information about the page than an ID or oddly named parameter would (2).” See: http://static.googleusercontent.com/media/www.google.com/en/us/webmasters/docs/search-engine-optimization-starter-guide.pdf

The link above is the Engine Optimization Starter guide. I encourage you to read it if you plan on taking SEO seriously.

The first step is setting the AngularJS $location API with “html5Mode” to “true” in “app/app.js”:

$locationProvider.html5Mode(true).hashPrefix(‘!’);                

Change the complete “app/app.js” location configuration tag from:

config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) {                  
  $locationProvider.hashPrefix('!');


  $routeProvider.otherwise({redirectTo: '/view1'});
}]);

To the following:

config(['$locationProvider', '$routeProvider', function($locationProvider, $routeProvider) {                
   $locationProvider.html5Mode(true).hashPrefix('!');
   $routeProvider.otherwise({redirectTo: '/view1'});
}]);

By default, “html5Mode” requires the use of <base href=”/” /> in the header, so add that to your code inside of the “index.html” file. We also want to give the search engines instruction to look for the Hashbang in the URL.

Consequently, the “SEOTester/app/index.html” file should include these two lines inside of the header tag:

<head>                
        <base href="/" />
             <meta name="fragment" content="!">
</head>

Next, we want to add a forward slash (“/”) to all of the links in our app—otherwise, it will add the link URL at the end of the address bar. On the index.html page, change from:

<body>                
...
    <li><a href="#!/view1">view1</a></li>
    <li><a href="#!/view2">view2</a></li>
...
</body>

To:

<body>                
...
    <li><a href="/#!/view1">view1</a></li>
    <li><a href="/#!/view2">view2</a></li>
...
</body>  

Now you are able to test the HTML5 mode by opening the index page again. In the WebStorm Terminal, type:

$ open http://localhost:8000                

As you can see, it has redirected the page to “localhost:8000/#!/view1,” then the page was redirected to “localhost:8000/view1” (Figure 8-2).

A416092_1_En_8_Fig2_HTML.jpg
Figure 8-2. Address bar without hashbang

Now that we are using the HTML5 mode, we can set routing with SEO-friendly URLs. Open “SEOTester/app/view1/view1.js” and add the following route provider command:

  $routeProvider.when('/view1/:id/:date/:title', {            
    templateUrl: 'view1/view1.html',
    controller: 'View1Ctrl'
  });

It’s common practice to send end users to a specific URL, passing information such as product ID and date. Our app will be able to query the database and render results. The title we are adding is not necessary, since we already passed a product ID, but it will make things more search-engine friendly. Your complete view1/view1.js should look like Listing 8-1.

Listing 8-1. view.js adding a redirect for a URL with information
'use strict';

angular.module('myApp.view1', ['ngRoute'])

.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/view1', {
    templateUrl: 'view1/view1.html',
    controller: 'View1Ctrl'
  });
  $routeProvider.when('/view1/:id/:date/:title', {
    templateUrl: 'view1/view1.html',
    controller: 'View1Ctrl'
  });
}])


.controller('View1Ctrl', [function() {

}]);

Now you can open the following URL:

http://localhost:8000/#!/view1/1/08-05-2016/some_title

In our code, we redirected to the same view1.html URL, and we can now extract the URL params inside of the controller. Inside of “view1/view1.js,” update the controller to the following code:

.controller('View1Ctrl', ['$scope','$routeParams', function($scope, $routeParams) {
      $scope.id =  $routeParams.id;
      $scope.date =  $routeParams.date;
      $scope.title =  $routeParams.title;
}]);

Listing 8-2 shows the complete view1/view1.js content:

Listing 8-2. view1.js complete code extracting params
'use strict';

angular.module('myApp.view1', ['ngRoute'])

.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/view1', {
    templateUrl: 'view1/view1.html',
    controller: 'View1Ctrl'
  });
  $routeProvider.when('/view1/:id/:date/:title', {
    templateUrl: 'view1/view1.html',
    controller: 'View1Ctrl'
  });
}])


.controller('View1Ctrl', ['$scope','$routeParams', function($scope, $routeParams) {
  $scope.id =  $routeParams.id;
  $scope.date =  $routeParams.date;
  $scope.title =  $routeParams.title;
}]);

Now that we’ve set these params in the controller, we can update the view “app/view1/view1.html” and display these params:

<p>This is the partial for view 1. </p>
<p>Id: {{id}}</p>
<p>Date: {{date}}</p>
<p>Title: {{title}}</p>

Redirect to the URL once again:

http://localhost:8000/#!/view1/1/08-05-2016/some_title

We can now see that the param has passed to the view (Figure 8-3).

A416092_1_En_8_Fig3_HTML.jpg
Figure 8-3. params passed to view

As you can see, we have passed params in the URL. In the controller, we can call a service that will call a database to retrieve results.

Snapshot

Our next step is to create snapshots of the app pages and give these to the search engine. The snapshot will have a pure static HTML code that the search engines will understand and crawl. If we leave the code as is with the hashbangs and AngularJS code, the search engine won’t be able to interpet the binding and variables we are passing, and the content will be missing on the page when the search engines crawl.

There are a few ways to do this, including using a hosted service that will cache your pages and serve them back to Google quickly.

Paid services will take care of a “PhantomJS” or a similar server as well as look after maintenance for a fee, or you can choose to do it on your own.

If you prefer to use a paid service, there are services such as “Prerender.io” ( https://prerender.io/ ), available. Their service is free when you use up to 250 pages and reasonably priced after that.

Often, you will want to serve thousands of pages and customize the process, so it’s good to know how to deploy the snapshots yourself.

This process can be broken down into the following steps:

  • Install and configure a PhantomJS server

  • Apply “angular-seo” script.

  • Create a deployment script.

  • Update the “.htaccess” file.

  • Submit the URL to Google.

Install and Config PhantomJS

Our first step is to install the PhantomJS server.

Note

PhantomJS is a headless WebKit (web browser) that is scriptable with a JavaScript API. It allows running a page without displaying the actual visual page on a browser.

A PhantomJS project can be seen here: https://github.com/amir20/phantomjs-node . To get started, install and run the following command:

$ sudo npm install -g phantom --save                                          

The command will install PhantomJS globally. During installation, it will show the location of PhantomJS in the Terminal console:

Done. Phantomjs binary available at /usr/local/lib/node_modules/phantom/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs

Copy the location, since we will configure the server to be accessible from any folder location on your machine. Call PhantomJS and be sure to point to the location of the libraries we have just copied:

$ /usr/local/lib/node_modules/phantom/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs

Once you run the command, the Terminal then replies with:

phantomjs>

This is a sign that PhantomJS is working correctly. Hit “control + c” to exit. It’s a good idea to copy PhantomJS to be accessible without typing in the entire location of PhantomJS; you can do so by copying PhantomJS to your local bin folder:

$ sudo cp /usr/local/lib/node_modules/phantom/node_modules/phantomjs-prebuilt/lib/phantom/bin/phantomjs /usr/local/bin/

Now, you can simply type “phantomjs” into the command line:

$ phantomjs

Hit “control + c” to exit.

Apply Angular-SEO Script

For our next step, we’ll install an open source code called an “angular-seo” library, which will utilize PhantomJS to render our app in HTML code. You can learn more about “angular-seo” projects here: https://github.com/steeve/angular-seo

To install “angular-seo,” we’ll use Bower. In the “SEOTester” project in WebStorm, type the following command into the Terminal to install “angular-seo”:

$ sudo bower install angular-seo --allow-root

Depending on the version you’re using, you may receive the following message:

Unable to find a suitable version for angular, please choose one by typing one of the numbers below:
    1) angular#1.3.13 which resolved to 1.3.13 and is required by angular-seo#d6b50c5b48
    2) angular#∼1.5.0 which resolved to 1.5.8 and is required by angular-seed
    3) angular#1.5.8 which resolved to 1.5.8 and is required by angular-route#1.5.

At the time of writing, “Angular-seed” in WebStorm is utilizing version “0.0.0” and AngularJS version “1.5.8.” Select option 2.

Now that installation is complete, you can test that the “Phantomjs” server is working properly with “angular-seo”; run the PhantomJS server on your machine and point to the “angular-seo” script and the local server:

$ phantomjs --disk-cache=no app/bower_components/angular-seo/angular-seo-server.js 8888 http://localhost:8000

If you're asked to allow incoming connections from PhantomJS, select "allow."

Hosts are pointing to localhost:8000. Now we need to test that the PhantomJS server is creating static pages and that we can point to a URL and serve pure HTML pages.

If everything is set correctly, once you go to your pages and replace the hashbang with _escaped_fragment_, you should see an HTML version of your page, clear of any AngularJS or Ajax codes.

For instance, take the page we previously created:

http://localhost:8000/#!/view1/1/08-05-2016/some_title

Open up a browser and type the following URL in a second tab in the browser:

http://localhost:8888/?_escaped_fragment_=/view1/1/08-05-2016/some_title

This may take a minute to open up, since it’s generating a static page. Now look at the page source of this page—in some browsers, you can add "view-source:" in front of the URL:

view-source:http://localhost:8888/?_escaped_fragment_=/view1/1/08-05-2016/some_title

As you can see, the entire code is static HTML, which the search engine is able to crawl.

For the data, you should see the static HTML code:

<p class="ng-binding ng-scope">Id: 1</p>
<p class="ng-binding ng-scope">Date: 08-05-2016</p>
<p class="ng-binding ng-scope">Title: some_title</p>
</div>

Deployment Script

Now that we can generate static pages, it’s good practice to tie the snapshots deployment process with the deployment script, so on each deployment a new set of snapshots will be updated.

There are many ways to create the deployment script, but we will be using Grunt to create our snapshots and upload these files to the server. The Grunt is broken down to create files, change the base, and upload to the server.

If you did not install GruntJS during our earlier chapters, you can do so by typing the following command:

$ sudo npm install -g grunt grunt-cli

To get Hello World, we’ll create a task to uglify and minify our app.js JavaScript file.

Create a Grunt build file, “SEOTester/Gruntfile.js,” and copy the code in Listing 8-3.

Listing 8-3. Gruntfile.js complete Hello World code
module.exports = function(grunt) {
    // Project configuration.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        uglify: {
            options: {
                banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */ '
            },
            build: {
                src: 'app/app.js',
                dest: 'build/<%= pkg.name %>.min.js'
            }
        }
    });


    // Load the plugin that provides the "uglify" task.
    grunt.loadNpmTasks('grunt-contrib-uglify');


    // Default task(s).
    grunt.registerTask('default', ['uglify']);


};

The Gruntfile.js file includes the following:

  1. Loading the plugins we’ll be using

  2. Project configurations

  3. Creating tasks

We will be using the uglify plugin, so first we want to install the plugin. In the WebStorm Terminal, type the following command:

$ sudo npm install grunt grunt-contrib-uglify --save-dev

Now we can run the default task:

$ grunt

After you run the command, you should get:

Running "uglify:build" (uglify) task
>> 1 file created.

Grunt has created a new file inside of “SEOTester/build/angular-seed.min.js.” The app.js file was uglified and minified, and a comment was added. See part of the file below:

/*! angular-seed 2016-08-21 */
"use strict";angular.module("myApp",["ngRoute","myApp.view1","myApp.view2",

Now, let’s continue by creating a task to handle the snapshots. These tasks will consist of the following:

  1. Clean folder directory

  2. Create a snapshot static page

  3. Upload files to server

First, we want to install the plugins we will be using: a cURL command and a shell command. Lucky for us, there are modules for that functionality. Install the modules using the following commands in the WebStorm Terminal:

$ npm install grunt-curl grunt-shell --save-dev

Copy the complete code shown in Listing 8-4 to the “Gruntfile.js” file on the root of the project:

Listing 8-4. Gruntfile.js generate snapshots files, complete code
module.exports = function(grunt) {
    // Project configuration.
    grunt.initConfig({
        pkg: grunt.file.readJSON('package.json'),
        uglify: {
            options: {
                banner: '/*! <%= pkg.name %> <%= grunt.template.today("yyyy-mm-dd") %> */ '
            },
            build: {
                src: 'app/app.js',
                dest: 'build/<%= pkg.name %>.min.js'
            }
        },
        shell: {
            clean_snapshots: {
                command: 'rm -rf /[PATH to project]/SEOTester/snapshots'
            },
            server_upload: {
                command: 'scp -r -i /[location to amazon key]/amazon-key.pem /[location to project]/SEOTester/snapshots/* ec2-user@[server ip]:/var/www/html/[location of snap folder]'
            }
        },
        curl: {
            'snapshots/view1': 'http://localhost:8888/?_escaped_fragment_=/view1/1/08-05-2016/some_title',
        }
    });


    // Load the plugin that provides the "uglify" task.
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-curl');
    grunt.loadNpmTasks('grunt-shell');


    // Default task(s).
    grunt.registerTask('default', ['uglify']);
    grunt.registerTask('snap', ['shell:clean_snapshots', 'curl' /*, 'shell:snap_upload'*/ ]);


};

Let's examine the code. First, we load the Grunt plugins. Add the following commands inside of the Grunt file:

grunt.loadNpmTasks('grunt-curl');
grunt.loadNpmTasks('grunt-shell');

Now we can register the task. We will call the task “snap”:

grunt.registerTask('snap', ['shell:clean_snapshots', 'curl' /*, 'shell:snap_upload'*/ ]);                                                                      

Notice that we have commented out the server upload for now, since we are going over the basics and will not actually upload the files to the server.

We’ll create the actual tasks of cleaning the snapshot folder and use a cURL command to generate a snapshot.

        shell: {
            clean_snapshots: {
                command: 'rm -rf /[path to project]/SEOTester/snapshots'
            },
            server_upload: {
                command: 'scp -r -i /[path to amazon key]/amazon-key.pem /[path to project]/SEOTester/snapshots/* ec2-user@[server ip]:/var/www/html/[path to snap folder]'
            }
        },
        curl: {
            'snapshots/view1': 'http://localhost:8888/?_escaped_fragment_=/view1/1/08-05-2016/some_title',
        }

Be sure to replace the path to project, path to snap folder, and server IP address or public DNS address. We are deploying the snap files to the Linux server we created in Chapter 2.

You can ensure the connection to the Linux server is still working by using the SSH connection shortcut we set in ∼/.ssh/config "$ ssh app." If you don't get a connection, make sure the server is still running in AWS and the SSH connection is open for your IP address. Refer to Chapter 2 for more details.

Keep in mind that for the script to work, you will need to still have two terminal windows open with the following commands:

$ npm start
$ phantomjs --disk-cache=no app/bower_components/angular-seo/angular-seo-server.js 8888 http://localhost:8000

We are downloading the files using cURL; Grunt has utilized the grunt-curl plugin for that. The task goes in and calls any page that we want to be crawled.

Lastly, take note that we want to upload these files to the server. We will be using a grunt-shell for this:

server_upload: {
  command: 'scp -r -i /[path to amazon key]/amazon-key.pem /[path to project]/SEOTester/snapshots/* ec2-user@[server ip]:/var/www/html/[path of snap folder]'
            }

Run the Grunt command:

$ grunt snap

The console will show you the following responses:

$ grunt snap
Running "shell:clean_snapshots" (shell) task


Running "curl:snapshots/view1" (curl) task
File "snapshots/view1" created.


Done.

Also, notice that a new folder has been created with the name “snapshots,” and it includes a snapshot of the view file, which includes the params we had passed through the URL (Figure 8-4).

A416092_1_En_8_Fig4_HTML.jpg
Figure 8-4. Snapshots folder created with a static file

When our app is created, we can use the server’s upload tasks to automatically upload all of these snapshots to the production server.

Update .htaccess

Apache web server, which we installed on the AWS Linux server, is a one of the most common web servers used to deploy web app pages, and we have already created a task to upload our static snapshots to that server.

All we have to do now is modify the .htaccess file and redirect any files that include _escaped_fragment_ to the corresponding snapshot. We want our deployment server to redirect from:

http://[some domain name]/view1/1/08-05-2016/some_title

to:

http://[some domain name]/?_escaped_fragment_=/view1/1/08-05-2016/some_title

This will use the file “view1” we’ve created, instead of the complete URL structure. Here is the .htaccess code snippet:

RewriteEngine On
RewriteCond %{REQUEST_URI}  ^/$
RewriteCond %{QUERY_STRING} ^_escaped_fragment_=/?([^/]+(?:/[^/]+|))
RewriteRule ^(.*)$ /snapshots/%1? [P,L]

Lastly, we want any hashbang URL to be redirected to the HTML none hashbang URL:

<IfModule mod_rewrite.c>
    Options +FollowSymlinks
    RewriteEngine On
    RewriteBase /
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_URI} !^/$
    RewriteRule (.*) /#!/$1 [NE,L,R=301]
</IfModule>

Set .htaccess Redirect

Listing 8-5 is an example of a complete “.htaccess” code that handles the redirect.

Listing 8-5. .htaccess complete redirect script
<IfModule mod_rewrite.c>
   <IfModule mod_negotiation.c>
      Options -MultiViews
   </IfModule>


   RewriteEngine On

   RewriteCond %{QUERY_STRING} ^_escaped_fragment_=/?([^/]+(?:/[^/]+|))
   RewriteRule ^(.*)$ /snapshots/%1? [NC,PT]


   # Redirect Trailing Slashes...
   RewriteCond %{REQUEST_FILENAME} !-d
   RewriteRule ^(.*)/$ /$1 [L,R=301]
</IfModule>


<IfModule mod_rewrite.c>
    Options +FollowSymlinks
    RewriteEngine On
    RewriteBase /
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_URI} !^/$
    RewriteRule (.*) /#!/$1 [NE,L,R=301]
</IfModule>

That’s it! The site should be set. You can confirm that it was successful by trying out links and seeing whether they have been redirected automatically, like this:

http://[some domain name]/?_escaped_fragment_=/view1/1/08-05-2016/some_title

AngularJS Metadata Tags

An important portion of an organic SEO is setting up the page’s HTML metadata tags—but what is the page metadata tag?

Note

Organic SEO is achieving high search engine placement/ranking in the unpaid section. You can achieve this by better understanding the search engine algorithm and applying principles that have proven to work.

The metadata tags are set as part of the <head> tag and are not visible to the end user, but they can provide search engines with information about your page. There are different types of metadata that you can include in each page, but the most well-known are title, description, and keywords. There are a few good practices to take into consideration regarding these metadata in order to achieve the best results possible.

The <meta> tag provides metadata with the HTML document. Metadata will not be displayed on the page, but will parse the machine. Meta elements are typically used to specify a page’s description, keywords, the author of the document, when it was last modified, and other metadata.

You are able to either roll your own solution or use open source solutions.

We are not done here yet. You probably want to customize each page to have its own unique metadata for the best exposure. There are many ways to do this, but we will show you two possible approaches—creating your own solution using the service module and using a solution library.

Update Metadata Using a Service Module

We can create a service factory method to help us handle the page’s metadata. Using a service module is great because it can provide us with a centralized script that will handle the data.

To get started, create a new service file and place it here: “SEOTester/app/components/services.js” (Listing 8-6).

Listing 8-6. Service factory to handle pages metadata
'use strict';

/* Services */

angular.module('myApp.services', [])
    .value('version', '0.1')
    .factory('Page', ['$rootScope', function ($rootScope) {


        var defaultTitle = 'defaultTitle',
            defaultDescription = 'defaultDescription',
            defaultKeywords = 'defaultKeywords';


        return {
            getDefaultTitle: function() {
                return defaultTitle;
            },
            getDefaultDescription: function() {
                return defaultDescription;
            },
            getDefaultKeywords: function() {
                return defaultKeywords;
            },
            setMeta: function(title, description, keywords) {
                $rootScope.meta = {
                    title: title,
                    description: description,
                    keywords: keywords
                }
            },
            setDefaultMeta: function() {
                $rootScope.meta = {
                    title: defaultTitle,
                    description: defaultDescription,
                    keywords: defaultKeywords
                }
            }
        };
    }]);

As you can see, we can set default values for title, description, keywords, and any other metadata you may want. We can get these values and also set our own custom values via setMeta and setDefaultMeta functions, as well as retrieve each metadata value.

Now, include a reference to the service file we created in the app index.html page.

  <script src="components/services.js"></script>

Also, bind the metadata values to the title, description, and keywords in the index.html “<head>” tag:

  <title>{{meta.title}}</title>
  <meta name="description" content="{{meta.description}}">
  <meta name="keywords" content="{{meta.keywords}}" />

Next, add a reference to the service module in the “app.js” file:

angular.module('myApp', [
  'ngRoute',
  'myApp.view1',
  'myApp.view2',
  'myApp.version',
  'myApp.services'
]).

Now we can use the service in any of the view scripts. See the “view1/view1.js” controller script we’re adding:

.controller('View1Ctrl', ['$scope', '$rootScope', '$routeParams', 'Page', function($scope, $rootScope, $routeParams, Page) {

  Page.setDefaultMeta();
  console.log($rootScope.meta);


  $scope.id =  $routeParams.id;
  $scope.date =  $routeParams.date;
  $scope.title =  $routeParams.title;
}]);

Test the results by opening the app again and inspecting the object we’ve placed at the console when we set the “console.log”, console.log($rootScope.meta) in the view1 controller (Figure 8-5).

A416092_1_En_8_Fig5_HTML.jpg
Figure 8-5. Console log values showing at the inspector
$ open http://localhost:8000

We can even build a custom solution where we pick the values out of our database. All we have to do is set the metadata in the controller to each page, or use the default values, in the “view1/view1.js” controller tag instead of what we have now: “Page.setDefaultMeta();” we can set the following:

Page.setMeta(title, description, Page.getDefaultKeywords() + hashtags);

And the hashtag variables can be fetched from an AngularJS service module. Don’t copy that code—it’s just for illustration purposes.

To actually see the values being parsed, we can create a snapshot and then look at the source file:

$ open http://localhost:8888/

You will see the default values we have assigned if you view the source file:

  <title class="ng-binding">defaultTitle</title>
  <meta name="description" content="defaultDescription">
  <meta name="keywords" content="defaultKeywords">

Update Metadata with ngMeta

The service solution works fine, but there are a few other good solutions on GitHub, such as “ngMeta” ( https://github.com/vinaygopinath/ngMeta ) and “ui-router-metatags” ( https://github.com/tinusn/ui-router-metatags ) that can also be utilized for updating metadata.

We will install and utilize ngMeta. To set the module, follow these steps:

Start by installing the module using npm:

$ bower install ngMeta --save

Next, in the index.html file, add ngMeta script:

<script src="bower_components/ngMeta/dist/ngMeta.min.js"></script>

Your complete code should appear as follows:

  <script src="bower_components/angular/angular.js"></script>
  <script src="bower_components/angular-route/angular-route.js"></script>
  <script src="app.js"></script>
  <script src="view1/view1.js"></script>
  <script src="view2/view2.js"></script>
  <script src="components/version/version.js"></script>
  <script src="components/version/version-directive.js"></script>
  <script src="components/version/interpolate-filter.js"></script>
  <script src="components/services.js"></script>
  <script src="bower_components/ngMeta/dist/ngMeta.min.js"></script>

Now, for the <head> metadata, replace the title, description, and keywords with the following:

  <title>{{ngMeta.title}}</title>
  <meta property="og:type" content="{{ngMeta['og:type']}}" />
  <meta property="og:locale" content="{{ngMeta['og:locale']}}" />
  <meta name="author" content="{{ngMeta.author}}" />
  <meta name="description" content="{{ngMeta.description}}" />

Then, add “ngMeta” to the modules in app.js and set the default values:

'use strict';

// Declare app level module which depends on views, and components
angular.module('myApp', [
  'ngRoute',
  'myApp.view1',
  'myApp.view2',
  'myApp.version',
  'ngMeta'
]).
config(['$locationProvider', '$routeProvider', 'ngMetaProvider', function($locationProvider, $routeProvider, ngMetaProvider) {
  $locationProvider.html5Mode(true).hashPrefix('!');
  $routeProvider.otherwise({redirectTo: '/view1'});


      ngMetaProvider.useTitleSuffix(true);
      ngMetaProvider.setDefaultTitle('Fallback Title');
      ngMetaProvider.setDefaultTitleSuffix(' - the best site');
      ngMetaProvider.setDefaultTag('author', 'Eli Elrom');
}])
.run(['ngMeta', function(ngMeta) {
  ngMeta.init();
}]);

Now we are able to use “ngMetaProvider” to set the default values and initialize the “ngMeta” module.

We can do so by setting the metadata to each page, as shown below for “view1/view1.js”:

'use strict';

angular.module('myApp.view1', ['ngRoute', 'ngMeta'])

.config(['$routeProvider', 'ngMetaProvider', function($routeProvider) {
  $routeProvider.when('/view1', {
    templateUrl: 'view1/view1.html',
    meta: {
      'title': 'title1',
      'description': 'description1'
    },
    controller: 'View1Ctrl'
  });
  $routeProvider.when('/view1/:id/:date/:title', {
    templateUrl: 'view1/view1.html',
    meta: {
      'title': 'title2',
      'description': 'description2'
    },
    controller: 'View1Ctrl'
  });
}])
.controller('View1Ctrl', ['$scope','$routeParams', function($scope, $routeParams) {
  $scope.id =  $routeParams.id;
  $scope.date =  $routeParams.date;
  $scope.title =  $routeParams.title;
}]);

Lastly, install the “ng-inspector” Chrome extension to see the values:

http://ng-inspector.org/

Now we can see the changing metadata (Figure 8-6).

A416092_1_En_8_Fig6_HTML.jpg
Figure 8-6. ng-inspector showing metadata values

http://localhost:8000/#!/view1/1/08-05-2016/some_title

Robots Instructions

There are instructions you can give to the search engines via the index.html meta tags. Additionally, you can provide the search engines with two files: “robots.txt” and “sitemap.xml.” In fact, it’s highly recommended that you create these files in your root folder.

Robots Meta Tags

Google recommends that you place instructions on your index.html page via meta tags. Normally, if you wanted to include the page in the index and follow any links, you would say:

<meta name="robots" content="index, follow">

Here is a handy list of instructions you can give to the robots:

  • NOINDEX: Don’t index the page.

  • NOFOLLOW: Don’t follow any links on the page.

  • NOARCHIVE: Don’t cache a copy of this page.

  • NOSNIPPET: Don’t include a description of the page.

Robots Exclusion Protocol

Create a file called “robots.txt,” place it on the root directory of your site, and tell the search engine any instructions you want to be followed. This process is called the “robots exclusion protocol.” The default file content will look like this:

User-agent: *
Disallow: /

You can then give specific instructions to disallow the crawling of certain pages:

User-agent: *
Disallow: /some-folder/

You can also explicitly disallow specific pages:

User-agent: *
Disallow: /some-folder/some-page.html

Sitemap

Search engines require that you provide a “sitemap.xml” file that includes instructions on how often you change the content of the site and the priority and location of your home page. Take a look at the sample code to visit your site on a daily basis and point to the site “view1” as your home page:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
    <loc>http://some-site.com/view1</loc>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
</url>
</urlset>

Social Media Meta Tags

It’s also good practice to leave instructions for popular social media robots. Listing 8-7 gives you an example of instructions for Google Plus, Facebook, and Twitter.

Listing 8-7. Meta tags for social media
<!-- Schema.org markup for Google+ -->
<meta itemprop="name" content="Site-name.com">
<meta itemprop="description" content="{{ meta.description }}">
<meta itemprop="image" content="//images/favicons/apple-touch-icon-120x120.png">


<!-- Twitter Card data -->
<meta name="twitter:card" content="summary" />
<meta name="twitter:site" content="@TwitterAccountName" />
<meta name="twitter:title" content="{{ meta.title }}">
<meta name="twitter:description" content="{{ meta.description }}">
<meta name="twitter:image" content="//images/favicons/apple-touch-icon-120x120.png" />
<meta name="twitter:url" content="http://some-site.com" />


<!-- Facebook data -->
<meta property="og:title" content="Site-name.com" />
<meta property="og:type" content="website" />
<meta property="og:url" content=https://Site-name.com />
<meta property="og:image" content=http://[icon path]/some-icon.ico />
<meta property="og:site_name" content="Site-name"/>
<meta property="og:description" content="{{ meta.description }}">

Notice that the data is binding to Angular $rootscope, so you can tie it to the values you set in “ngMeta” or the service metadata example I provided you with.

Webmasters

Webmaster is responsible for the submission of pages to search engine robots. Google is the most notable search engine out there, so we will be focusing on Google for the submission of our pages. However, it’s good practice to educate yourself on every single search engine you are targeting for optimal results. Luckily, Google is very transparent in regards to the submission of pages; you can use their webmaster tool dashboard to see how Google robots will crawl your site and then see if anything requires changes. You will first need to create an account or sign in to the webmasters section: https://www.google.com/webmasters/ .

Next, you can add a “property,” meaning an app or a site (Figure 8-7).

A416092_1_En_8_Fig7_HTML.jpg
Figure 8-7. Google Webmasters add a property

A property can be a web site or an Android app. After verifying your properties, you can log back in and enter the following URL into the dashboard area:

https://www.google.com/webmasters/tools/dashboard

Submit Pages to Google

On the left menu, there is a link to “Crawl” and “Fetch as Google” (Figure 8-8).

A416092_1_En_8_Fig8_HTML.jpg
Figure 8-8. Fetch as Google in Google Webmaster.

In the “Fetch as Google” functionality, we are able to “Fetch and Render” the pages as a Google spider renders the page and displays any issues in reaching resources.

We would then submit the URL with the “escaped fragment” as the URL and our “.htaccess” will be redirected to the snapshots:

http://[somewebsite]/?_escaped_fragment_=/view1/1/08-05-2016/some_title

Ensure Successful Page Submission

After the submissions have been made, we can ensure that the page has been submitted to Google correctly and has been crawled by the search engine as expected, meaning you can check to see that your site’s pages actually show up on Google. To do so, you’ll use the following search:

https://www.google.com/#q=site:somesite.com

As a result, Google will return all pages that were crawled. Note that it may take Google few days to show the results.

Summary

In this chapter, we covered AngularJS SEO, configured AngularJS redirect settings, and began a new test AngularJS seed project. We covered the different options available in AngularJS to use HTML5 mode, as well as Hashbang mode. We learned how to create a snapshot of our pages by installing and configuring a “PhantomJS” server, applied “angular-seo” script and even applied a Grunt deployment script for each upload and snapshot of our pages. We also learned about how to set and redirect pages using the “.htaccess” file.

Additionally, we learned how to set AngularJS meta tags, update the meta tags using a service module, and update the meta tags with the “ngMeta” module. We studied the robot exclusion protocol and learned about setting specific instructions for search engines and social media robots.

Lastly, we learned about the Google Webmasters service, how to submit pages to Google robots, and how to ensure the pages’ submissions were successful.

In the next chapter we will be covering build scripts.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.138.104