Angular Prerender SEO Practice

Source: Internet
Author: User

Leading 0

Angular.js good, but a little bad is, for SEO unfriendly, because angular more suitable for the SPA single page application. In this case, all HTML is dynamically generated using angular. Therefore, the search engine has no way to index the entire site.

For this question, I read an article on JavaScript SEO. After reading this article, for the use of angular SEO, have a simple understanding. And see the line has been running a site http://answers.gethuman.com/, know according to the article is completely can be both search engine friendly, but also can fully play the advantages of angular, to build a single page application.

After communicating with the blogger, I learned some details, and I want to experiment with an example. So I have made some attempts, in the process of trying, naturally encountered some problems. After a step by step search and solve, now for Angular single page application SEO problem has a general understanding, so here to record.

Procedure 1-Implementing back-end PreRender

It should not be too difficult to implement this idea, and my approach is to use EJS for rendering at the back end, which is the rendering of the angular itself. So although there will be two sets of templates, but in fact, the cost is not big, after the explanation can understand.

For the data source, my approach is to have a data acquisition layer at the backend, an API layer . At the front end is the angular data layer .

    1. The data acquisition layer of the backend is only responsible for acquiring the logical part of the data, and the output is structured data.
    2. The backend API layer, to the above data acquisition layer, JSON or JSONP packaging, return to the front end.
    3. The data acquisition of the front-end angular is obtained through the API layer in 2.

The rendering process is:

    1. Back-end Ejs part, directly through the back-end data acquisition layer, get the data to render.
    2. The angular part of the front-end, then obtains the data through the backend API layer, carries on the front-end rendering.

Because the backend API layer is simply a JSON or JSONP package for data, the data in front and back is actually the same . This ensures that the logic of the two sets of templates is the same for both front and back ends, just some simple differences between the ejs and angular template syntax, such as loops, if judgments, and so on. Just take one of the templates and turn the syntax into a different one, so the personal feeling is not too big for the cost of maintenance.

Procedure 2-front-end angular rendering issues

Front-End If you want to use angular for data binding, user interaction, and so on, you need to let angular take over all or part of the page. Since I am completely using angular + angular-uirouter here, this is where the entire page is taken over.

But here's a question.

If the content of the back-end rendering is populated in Ui-view, the data needed to render the page angular after the page is loaded, and the process is obtained through the interface, but angular will clean up the contents of the Ui-view before rendering. Will cause the page just entered is normal, and then the page suddenly blank for a period of time (at this time in the data acquisition), and then load the problem again.

If the content of the back-end rendering is placed separately in one part of the page, this part is not controlled by angular. At the same time, angular will render a copy of the same template, causing the template to repeat the problem.

So in order to solve this problem, I carried out a small hack.

I write the entire page structure like this

<body ng-controller="topCtrl">    <div ui-view ng-hide="initLoad"></div>    <div ng-if="initLoad"><!-- 这里是后端模板渲染的部分. -->    </div></body>

The JS section is written like this

Angular.module (' demo ', [' Ngresource ', ' Ui.router ']). config ([' $stateProvider ', function ($stateProvider) {$  Stateprovider.state (' state1 ', {url: '/state1/:p aram1 ', Templateurl: ' tpl/template.html ', controller: ' Democtrl '}]) factory (' Resource1 ', [' $resource ', function ($resource) {return $resource ('/api/:p aram1 ', {p ARAM1: ' @param1 '});}]). Controller (' Topctrl ', [' $scope ', ' $rootScope ', function ($scope, $rootScope) {//Initload determines the first time the page is loaded,    Angular does not clear the back-loaded pages.    When the page is loaded, set Initload to False, and the next time the angualr operation is done,//You can automatically clean up what the backend renders.    var initload = $scope. Initload = true; $scope. Markinit = function () {//If it is the first time it is loaded, just update the tag here, and then return directly,///When you execute this method the next time, you will need to use angular render ui-view to replace the modulo of the back-end rendering            Board if (initload) {initload = false;        Return        }//When the value of $scope.initload becomes false, angular will automatically clean up the back-end rendered template.    The front-end template $scope rendered with Ui-view is then shown. Initload = false;    }; $rootScope. $on (' $stateChangeStart ', functiOn () {$scope. Markinit (); });}]).    Controller (' Democtrl ', [' $scope ', ' Resource1 ', ' $stateParams ', function ($scope, Resource1, $stateParams) {    Resource1.query ({param1: $stateParams. param1}). $promise. Then (data) {$scope. data = data; })    // ...}])

The idea is to let the Ui-view part first hide, showing only the back-end rendering part. The current side has done some operations, need to jump to the other state of Ui-view, and then the service side rendered HTML removed.

The key part is initLoad this thing in Topctrl. Let's set this variable to TRUE or false to ensure that the ui-view part is hidden or displayed.

When angular and Uirouter initialize the page, $rootScope triggers $stateChangeStart the event, and we use this event to know whether the currently displayed page is rendered from the server or later by angular.

The first time this is triggered, angular is making the first render, $scope.initload should not be set to true, so we just set initload this temporary variable to false, $scope. Initload is still true.

The next time the trigger, the first check initload this variable, this is false, the proof is not the first load, so you need to set $scope.initload to false. Once $scope.initload becomes false, Ng-if will work, cleaning up the back-end rendering template, and displaying the angular rendered template.

In this way, the problem that is mentioned at the beginning of Procedure 2 is basically solved.

Procedure 3-Ensure that user interaction is still available after the first load.

In procedure 2, only the backend render template does not conflict with the front-end render template, but it does not resolve a problem. How do you ensure that the first loaded back-end template does not clean up correctly in response to the user's click DblClick these operations? These parts are not under the control of the Ui-view controller.

Workaround, take advantage of the inheritance characteristics of $scope.

The entire code is modified to the following.

Angular.module (' demo ', [' Ngresource ', ' Ui.router ']). config ([' $stateProvider ', function ($stateProvider) {$ Stateprovider.state (' state1 ', {url: '/state1 ', Templateurl: ' tpl/template.html ', Controller: ' Democt RL '})}] Factory (' Resource1 ', [' $resource ', function ($resource) {return $resource ('/api/:p aram1 ', {param1: ') @param1 '}). Controller (' Topctrl ', [' $scope ', ' $rootScope ', function ($scope, $rootScope) {//Initload determines the first time the page is loaded,    Angular does not clear the back-loaded pages.    When the page is loaded, set Initload to False, and the next time the angualr operation is done,//You can automatically clean up what the backend renders.    var initload = $scope. Initload = true; $scope. Markinit = function () {//If it is the first time it is loaded, just update the tag here, and then return directly,///When you execute this method the next time, you will need to use angular render ui-view to replace the modulo of the back-end rendering            Board if (initload) {initload = false;        Return        }//When the value of $scope.initload becomes false, angular will automatically clean up the back-end rendered template.    The front-end template $scope rendered with Ui-view is then shown. Initload = false;    };       $scope. Addmethod = function (Evtname, func) { Here this refers to the Ui-view corresponding controller in $scope this[evtname] = func;    $scope [Evtname] = func;    };    $rootScope. $on (' $stateChangeStart ', function () {$scope. Markinit (); });}]). Controller (' Democtrl ', [' $scope ', ' Resource1 ', ' $stateParams ', function ($scope, Resource1, $stateParams) {$    Scope.addmethod (' clickimg ', function () {alert (' click img ');    });    Resource1.query ({param1: $stateParams. param1}). $promise. Then (data) {$scope. data = data; })    // ...}])

So, if the back-end rendering section is as follows

<div ng-if="initLoad"><!-- 这里是后端模板渲染的部分. -->    </div>

After this modification, Ui-view controller adds a method, the upper Topctrl can add the same method, will be able to correctly respond to the user's actions.

It's just that there's a downside to this method of modification. If I first write a front-end template and then convert it into the syntax of the Ejs template, I need to decide which angular syntax needs to be translated and which angular syntax needs to be preserved in order to respond correctly to user actions.

Of course, this is not a problem in order to achieve the ultimate goal of using both angular and SEO friendliness.

Process 4-ngcloak

The basic problem is solved, then write a page. At this point the page can be back-end PreRender, the first time to enter the page, there is no page flashing phenomenon, but also to correctly respond to some of the user's actions, it appears that everything seems to be perfect. However, there are still many problems.

Page flashing, where the page flashing, is the subsequent operation of the page flashing, from a ui-view state to another state, as said before, angular will be the contents of the page to clean up, and then render. Instead, when everything is ready to be rendered, clear the contents of the page.

Using angular ui-view flicker keywords to search, found the use of Ng-cloak to solve the method, but I tried, basically no effect. Because, the essence of Ng-cloak is a class, in the process of rendering, is the display:none state, when the rendering is finished, the class is removed .

It seems that this thing does not solve the problem I said, either, first clean up the contents of the page before rendering. Because of the rendering process, we need to get the data to the server side, so the whole page is white in this process.

The resolve of the process 5-ui-router

After another search, search for a thing in Ui-router, resolve through the document can be seen, this thing, is to ensure that the Ui-view corresponding controller initialization, all dependencies have been loaded.

The documentation is as follows

You can use resolve to provide your controller with content or data, is the custom to the state. Resolve is an optional map of dependencies which should was injected into the controller.

If any of these dependencies is promises, they would be resolved and converted to a value before the controller is instant Iated and the $routeChangeSuccess event is fired.

So I changed the entire JS code into this.

Angular.module (' demo ', [' Ngresource ', ' Ui.router ']). config ([' $stateProvider ', function ($stateProvider) {$ Stateprovider.state (' state1 ', {url: '/state1 ', Templateurl: ' tpl/template.html ', Controller: ' Democt RL ', resolve: {//here for Resource1data to get work resource1data: [' Resource1 ', ' $stateParams ', funct                Ion (Resource1, $stateParams) {return resource1.query ({param1: $stateParams. param1            }). $promise; }]}) factory (' Resource1 ', [' $resource ', function ($resource) {return $resource ('/api/:p aram1 ', {p ARAM1: ' @param1 '});}]). Controller (' Topctrl ', [' $scope ', ' $rootScope ', function ($scope, $rootScope) {//Initload determines the first time the page is loaded,    Angular does not clear the back-loaded pages.    When the page is loaded, set Initload to False, and the next time the angualr operation is done,//You can automatically clean up what the backend renders.    var initload = $scope. Initload = true; $scope. Markinit = function () {//If it is the first time it is loaded, just update the tag here and then return directly,//When you execute this method the next time, you need to use the AngUlar render Ui-view to replace the back-end rendered template if (initload) {initload = false;        Return        }//When the value of $scope.initload becomes false, angular will automatically clean up the back-end rendered template.    The front-end template $scope rendered with Ui-view is then shown. Initload = false;    };        $scope. Addmethod = function (Evtname, func) {this[evtname] = func;    $scope [Evtname] = func;    };    $rootScope. $on (' $stateChangeStart ', function () {$scope. Markinit (); });}]). Controller (' Democtrl ', [' $scope ', ' resource1data ', function ($scope, resource1data) {//This is no longer injected Resource1 and $stateparams    , but directly into the Resource1data $scope defined in resolve. Addmethod (' clickimg ', function () {alert (' click img ');    });    $scope. data = Resource1data; // ...}])

After the above modification, we can guarantee that when the page is switched, it will first get all the injected items required by the Ui-view corresponding controller, and when all the injected items are already resolve state, then the controller initialization is done. This way, the problem of flashing the page is resolved.

Procedure 6-The perfect solution

Through the above resolve scheme, since it is able to solve the subsequent page switching between the page flicker problem, that can solve the page when the first load of the page flashing problem? Because the page loaded on the homepage cooling is also due to resource to get the data caused.

So, experiment with the HTML code to change to the following

<body>    <div ui-view>        <!-- 这里是后端模板渲染的部分. -->    </div></body>

The JS code is modified to read as follows

  angular.module (' demo ', [' Ngresource ', ' Ui.router ']). config ([' $stateProvider ', function ($stateProvider) {$stateProvider. State (' state1 ', {url: '/state1 ', Templateurl: ' tpl/template.html ', Controller: '  Democtrl ', resolve: {//Get work done resource1data here Resource1data: [' Resource1 ', ' $stateParams ',                 function (Resource1, $stateParams) {return resource1.query ({param1: $stateParams. param1            }). $promise;        }]). Factory (' Resource1 ', [' $resource ', function ($resource) {return $resource ('/api/:p aram1 ', { param1: ' @param1 '});}]).        Controller (' Democtrl ', [' $scope ', ' resource1data ', function ($scope, resource1data) {$scope. clickimg = function () {    Alert (' click img ');    } $scope. data = Resource1data; // ...}])

After the test, the first page load when the screen flashing problem can also be resolved. Through the above method, also does not need Topctrl, because the page loads, the angular will also render again, but here the rendering process does not appear the page flashes, the user almost does not realize the entire page from the back-end template to the front-end template transition process. The hack that the backend template responds correctly to user actions can also be removed.

These are some of the research I have done to achieve angular prerender seo, and the hack to achieve some of the goals, and to explore and find a better solution. Although some places are quite simple to read, it seems like a stroke, but the thinking is really not easy.

» Original Address of this article: http://ISay.me/2014/06/angular-prerender-seo-and-use-resolve-for-page-flicker.html

Angular Prerender SEO Practice

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.