Giter Club home page Giter Club logo

angular-seo's Introduction

Angular-SEO

SEO for AngularJS apps made easy. Based on PhantomJS and yearofmoo's article.

Requirements

You will need PhantomJS to make this work, as it will render the application to HTML.

How to use

The solution is made of 3 parts:

  • small modification of your static HTML file
  • an AngularJS module, that you have to include and call
  • PhantomJS script

Modifying your static HTML

Just add this to your <head> to enable AJAX indexing by the crawlers.

    <meta name="fragment" content="!" />

AngularJS Module

Just include angular-seo.js and then add the seo module to you app:

angular.module('app', ['ng', 'seo']);

If you are using RequireJS, the script will detect it and auto define itself BUT you will need to have an angular shim defined, as angular-seo requires it:

requirejs.config({
    paths: {
        angular: 'http://cdnjs.cloudflare.com/ajax/libs/angular.js/1.0.3/angular.min',
    },
    shim: {
        angular: {
            exports: 'angular'
        }
    }
});

Then you must call $scope.htmlReady() when you think the page is complete. This is necessary because of the async nature of AngularJS (such as with AJAX calls).

function MyCtrl($scope) {
    Items.query({}, function(items) {
        $scope.items = items;
        $scope.htmlReady();
    });
}

If you have a complicated AJAX applicaiton running, you might want to automate this proccess, and call this function on the config level.

Please that this is only a suggestion we've came up with in order to make your life easier, and might work great with some set-ups, while not working at all with others, overall, you should try it yourself and see if it's a good fit for your needs. There's alwasys the basic setup of calling $rootScope.htmlReady() from the controller.

Example:

var app = angular.module('myApp', ['angular-seo'])
.config(function($routeProvider, $httpProvider){
    $locationProvider.hashPrefix('!');
    $routeProvider.when({...});

    var $http,
            interceptor = ['$q', '$injector', function ($q, $injector) {
                var error;
                function success(response) {
                    $http = $http || $injector.get('$http');
                    var $timeout = $injector.get('$timeout');
                    var $rootScope = $injector.get('$rootScope');
                    if($http.pendingRequests.length < 1) {
                        $timeout(function(){
                            if($http.pendingRequests.length < 1){
                                $rootScope.htmlReady();
                            }
                        }, 700);//an 0.7 seconds safety interval, if there are no requests for 0.7 seconds, it means that the app is through rendering
                    }
                    return response;
                }

                function error(response) {
                    $http = $http || $injector.get('$http');

                    return $q.reject(response);
                }

                return function (promise) {
                    return promise.then(success, error);
                }
            }];

        $httpProvider.responseInterceptors.push(interceptor);

And that's all there is to do on the app side.

PhantomJS Module

For the app to be properly rendered, you will need to run the angular-seo-server.js with PhantomJS. Make sure to disable caching:

$ phantomjs --disk-cache=no angular-seo-server.js [port] [URL prefix]

URL prefix is the URL that will be prepended to the path the crawlers will try to get.

Some examples:

$ phantomjs --disk-cache=no angular-seo-server.js 8888 http://localhost:8000/myapp
$ phantomjs --disk-cache=no angular-seo-server.js 8888 file:///path/to/index.html

Testing the setup

Google and Bing replace #! (hashbang) with ?_escaped_fragment_= so http://localhost/app.html#!/route becomes http://localhost/app.html?_escaped_fragment_=/route.

So say you app is running on http://localhost:8000/index.html (works with file:// URLs too). First, run PhantomJS:

$ phantomjs --disk-cache=no angular-seo-server.js 8888 http://localhost:8000/index.html
Listening on 8888...
Press Ctrl+C to stop.

Then try with cURL:

$ curl 'http://localhost:8888/?_escaped_fragment_=/route'

You should then have a complete, rendered HTML output.

Running in behind Nginx (or other)

Of course you don't want regular users to see this, only crawlers. To detect that, just look for an _escaped_fragment_ in the query args.

For instance with Nginx:

if ($args ~ _escaped_fragment_) {
    # Proxy to PhantomJS instance here
}

githalytics.com alpha

angular-seo's People

Contributors

darul75 avatar freshtonic avatar jrgtt avatar matthewrayfield avatar mingstar avatar mnort9 avatar steeve avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

angular-seo's Issues

Not working with Node/ExpressJS/MongoDB setup

My Angular app is built using Node.JS with ExpressJS and calls in data from MongoDB (via MongoLabs).

I'm trying to use this method as described in your readme, but I can't get it to work. I've never used PhantomJS so I may be doing this wrong, here are my steps:

  • Added 'seo' to my app's modules
  • I'm using $locationProvider.html5Mode(true);, so I have <meta name="fragment" content="!"> in my head.
  • I'm using your $scope.htmlReady(); in my controller
  • I installed PhantomJS using Homebrew
  • My app is running via node, at http://localhost:5000.
  • I run phantomjs --disk-cache=no angular-seo-server.js 5000 http://localhost:5000/
  • I try to curl my search page, curl http://localhost:5000/?_escaped_fragment_=/search%3Fgender=Womens/0, and I see my template come up but I see none of my partials (just the base template with my head, body tag, scripts and footer), and there are a few {{curly braces}} in there that load in data from Angular but these are not rendered in my Curl request.

Is this an issue in using html5Mode(true) urls? According to this article it should be fine: http://www.yearofmoo.com/2012/11/angularjs-and-seo.html

How to pass cookies with request?

Currently angual-seo fails on pages that require user login. Is there any way to get cookies from request (came from nginx) and pass it to render function?

Rendering too slow

Hello,

I'm try to integrate the module. It's working but the problem is that it is very very slow ... around 40 seconds to render the page, I think the Google bot won't like it.
Do you have an idea why it is that slow ?

Thanks.

Adding bower.json and package.json files

Would you like me to do that, and also wrap the server and client in 2 separate angular modules?
(Of course I will update the docs and etc.)

Also I feel kind of bad about the syntax error that I've pushed to this repo, would you like me to add some basic unit tests?.

?_escaped_fragment_= is not working https

when i hit url:
http://example.com/?_escaped_fragment_=
but
only In https request time not working .
https://example.com/?_escaped_fragment_=

that time page is not render. how can resolve this issue.
please help

my .htacesss file
RewriteEngine On
RewriteCond %{HTTPS} !=on

RewriteRule ^(.)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
# Tried for SSR -Verishal
Options +FollowSymLinks
RewriteCond %{REQUEST_URI} ^/$
RewriteCond %{QUERY_STRING} ^escaped_fragment=/?(.
)$
#RewriteCond %{QUERY_STRING} escaped_fragment=/([^&])
RewriteRule ^/(.
) http://example.com:9090/$1 [R,L]
ProxyPassReverse / http://example.com:9090/
Options Indexes FollowSymLinks MultiViews

Production envirment url:
phantomjs --disk-cache=no --ssl-protocol=TLSv1 --ignore-ssl-errors=true --web-security=false /opt/bitnami/apache-tomcat/staging/ROOT/js/angular-seo-server.js 9090 https://example..com/

RequireJS

Is there any way to avoid this dependency?

How to start angular-seo from init.d ?

Hi all,

a recommended way to run angular-seo is -

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

But it waits for user input (Ctrl-C) and so I'm not sure it can be used in init.d scripts.

So what is the right way to run angular-seo when server starts?

Doesnt seem to route properly when html5mode is activated

I can see this code in the server.js file :

var route = parse_qs(request.url)._escaped_fragment_;
var url = urlPrefix
  + request.url.slice(1, request.url.indexOf('?'))
  + '#!' + decodeURIComponent(route);
renderHtml(url, function(html) {
    response.statusCode = 200;
    response.write(html);
    response.close();
});

Clearly, this creates a route prefixed with '#!' which breaks my router which is configured for urls like localhost:8080/signup and not localhost:8080#!/signup.

This results in having only the / url supported and properly indexed. Any other url will simply render / content...
I tried to remove '#!' from your source code in the server to test it, and it works fine now.

This needs to be compatible with html5mode now.

Great work though, it served me a lot on multiple projects, thanks !

Issue with Google plus and Slack

My web use angular-seo.js and config htm5 link
$locationProvider.html5Mode(true);

My issue is when i copy my link and paste into the shared box on Google plus, Google plus don't parse my website's title and description, and when i do the same thing with Slack chat , Slack display
{{ $root.pageTitle }}
{{ $root.pageDescription }}

With facebook, linkedin, it's normal.
This is my <head> part:
<meta name="fragment" content="!">
<title ng-bind="$root.pageTitle"></title>

<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="chrome=1">
<base href="/">

<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="My description">
<meta name="author" content="{{ $root.author }}">
<meta name="copyright" content="© Ideapod {{ $root.today | date:'yyyy' }}">

<meta property="fb:app_id" content="12345678">
<meta property="og:site_name" content="MyWeb">
<meta property="og:image" content="{{ $root.pageImage }}">
<meta property="og:type" content="{{ $root.facebookOpenGraph.type }}">
<meta property="og:url" content="{{ $root.facebookOpenGraph.url }}">
<meta property="og:title" content="{{ $root.pageTitle }}">
<meta property="og:description" content="{{ $root.pageDescription }}">

What's wrong with my code, please let's me know. Thank you very much !

?_escaped_fragment_= is not working https

i have follow your tutorial . i got good help. but i stunk small issue.
why not take ?escaped_fragment= in https, but In still working in http. because i'm generate html snapshot ,In phantomjs throw. i had major problem in https.
how can resolve .please help me.
i have this htaccess:

# Redirect "/blog" "http://blog.xyz.com/" DocumentRoot "/opt/bitnami/apache2/htdocs" ServerName www.xyz.com RewriteEngine On RewriteCond %{HTTPS} !=on

#RewriteRule ^(.*)$ http://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Tried for SSR - Verishal

Options +FollowSymLinks
RewriteCond %{REQUEST_URI} ^/$
RewriteCond %{QUERY_STRING} ^escaped_fragment=/?(.)$
#RewriteCond %{QUERY_STRING} escaped_fragment=/([^&]
)
RewriteRule ^/(.*) http://www.xyz.com:9090/$1 [NC,L]
ProxyPassReverse / http://www.xyz.com:9090/
Options Indexes FollowSymLinks MultiViews

RewriteCond %{QUERY_STRING} ^escaped_fragment=/?(.*)$

RewriteRule ^(.*)$ /snapshots/%1? [NC,L]

#Options Indexes FollowSymLinks MultiViews

#<directory "d:="" snapshots"="">
#Allow from all
#Require all granted
#

.htaccess example

can we have an .htacess example to tell the crawler in wich server loock for cached pages?

status Code Error

i am getting this error
cannot access member `status Code' of deleted QObject

Using angular

Is there a reason the ready event depends on angular and is set up as an angular module? I assume a regular javascript event would work just as well? This way we can avoid having to inject the seo module everywhere.

I assume this would be easier.

Create a tag of the project

Hi there

Have you considered creating a tag of a version of the project ?
Because putting a dependency without a version through bower is the best way to have inconsistencies between environment and is a real pain to debug.

thx

[enhancement] Add missing bower.json.

Hey, maintainer(s) of steeve/angular-seo!

We at VersionEye are working hard to keep up the quality of the bower's registry.

We just finished our initial analysis of the quality of the Bower.io registry:

7530 - registered packages, 224 of them doesnt exists anymore;

We analysed 7306 existing packages and 1070 of them don't have bower.json on the master branch ( that's where a Bower client pulls a data ).

Sadly, your library steeve/angular-seo is one of them.

Can you spare 15 minutes to help us to make Bower better?

Just add a new file bower.json and change attributes.

{
  "name": "steeve/angular-seo",
  "version": "1.0.0",
  "main": "path/to/main.css",
  "description": "please add it",
  "license": "Eclipse",
  "ignore": [
    ".jshintrc",
    "**/*.txt"
  ],
  "dependencies": {
    "<dependency_name>": "<semantic_version>",
    "<dependency_name>": "<Local_folder>",
    "<dependency_name>": "<package>"
  },
  "devDependencies": {
    "<test-framework-name>": "<version>"
  }
}

Read more about bower.json on the official spefication and nodejs semver library has great examples of proper versioning.

NB! Please validate your bower.json with jsonlint before commiting your updates.

Thank you!

Timo,
twitter: @versioneye
email: [email protected]
VersionEye - no more legacy software!

Cache

Any ideas how to setup cache for rendered html on nginx level and how to flush it when it's needed? On high load websites calling phantomjs instance with each request can be bad idea, am i right?

Cannot get all my pages indexed

Hi,

Thanks for making this tool. I am trying to use it to index my app but somehow I can only get the homepage indexed.

As I am not using hashbangs, I've added the following meta tag to my app's header:

<meta name="fragment" content="!" />

In my main controller, I trigger $scope.htmlReady when the content is fully loaded:

  $scope.$on('$viewContentLoaded', function() {
    $scope.htmlReady();
  });

I have loaded the seo module properly and while my server's app listens on port 4000 (I use an express server), I launch phantomJS on port 4040 with the following command:

phantomjs --disk-cache=no ./bin/angular-seo-server.js 4040 http://127.0.0.1:4000

If I check how things are working for the homepage with a simple curl 'http://127.0.0.1:4040/?_escaped_fragment_=' I get the correct HTML rendered properly.

But if I try a different route like http://127.0.0.1:4040/test?_escaped_fragment_= I only get html <html><head></head><body></body></html> while http://127.0.0.1:4000/test works fine.

How can I make sure all my pages are indexed and not only my homepage?

angular seo static content

Hello,

Is there any way I can use angular-seo without a custom proxy server? For example, can I dump static files to be served for the crawlers so that I can put entire content onto github pages?

As far as I understood, then uwing angular-seo, there must be 2 servers running:

  • the application itself (24/7)
  • phantomjs, which serves the HTML of the pages (also 24/7)

Please instruct me whether I can dump this content somehow.

http.get to a .json file

Interestingly enough, if a controller pulls data from a database (example, mysql) it will work just great. If the data is pulled from a .json file it causes it to not pull the compiled page.

Is this a bug or am I doing something wrong?

How to get this to work with Angular 1.3.6

Angular's $httpProvider.responseInterceptors is deprecated. I tried to use the new version which is $httpProvider.interceptors, but I cannot seem to get this to work. When I curl the application, I receive the original template.

Doesn't work with facebook link debugger

I've implemented this following your instructions (implemented the interceptor solution) but every time i try to fetch og meta tags with the facebook link debugger it returns empty angular template tags {{ var }}.

Tested it out with curl 'MY_URL' and it did render the page so i'm guessing angular/nginx is setup ok.

Any suggestions on why this could be happening?

Prevent the angular-seo script from running when the client is not a bot

Some non-modern browsers have an issue with the createEvent and dispatchEvent functions.

For instance IE8 throws a whole bunch of errors.

To fix this I suggest to implent a feature that prevents the script from running when the client is not a bot.

currently I am using the following setup to fix this:

    <script src="lib/angular-seo/angular-seo-mock.js"></script>
     <!--[if !IE]>
        <script src="lib/angular-seo/angular-seo.js"></script>
     <![endif]-->   

angular-seo-mock.js:

angular.module('seo', []).run(function($rootScope){
    $rootScope.htmlReady = function(){
        return false;
    }
});

Understanding angular-seo

Hi,
Thanks for this really useful plugin. Helps me a lot.
I've a question on how this actually works in practice. Say, I have an angular website running at http://example.com and the phantomjs-angular-seo server running at http://example.com:8888. How does a crawler know on which port it the generated HTML is served? I mean, if i run curl 'http://example.com:8888/?_escaped_fragment_=/view/515/52.5199-13.4386' I get the generated HTML, but how does Google, FB or Bing know where to look for it? Is 8888 the default port for that?

Would be great, if someone could shed some light on my misunderstanding.
Thanks again,
Daniel

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.