AngularJS SEO pour les pages Web statiques (S3 CDN)

j'ai cherché des moyens d'améliorer le référencement des applications angularJS hébergées sur un CDN comme Amazon S3 (c'est-à-dire un stockage simple sans backend). La plupart des solutions là-bas, PhantomJS , pré-rendu.io , seo.js etc., s'appuyer sur un backend pour reconnaître l'url ?_escaped_fragment_ que le crawler génère et ensuite aller chercher la page pertinente ailleurs. Même grunt-html-snapshot ultimement a besoin de vous pour le faire, même si vous générez les pages de snapshot à l'avance.

Ce solution c'est en s'appuyant sur l'utilisation de cloudflare comme un reverse proxy, ce qui semble un peu du gaspillage étant donné que la plupart de l'appareil de sécurité, etc. que leur service fournit est totalement inutile pour un site statique. La mise en place d'un mandataire inversé moi - même comme suggéré ici semble également problématique étant donné qu'il faudrait soit i) routage de toutes les applications AngularJS j'ai besoin de html statique par le biais d'un serveur proxy qui pourrait entraver la performance ou ii) mise en place d'un serveur proxy distinct pour chaque application, à quel point je peut ainsi définir un arrière-plan, ce qui n'est pas abordable à l'échelle, je suis en train de travailler.

y a-t-il de toute façon de faire cela, ou est-ce que les applications AngularJS hébergées statiquement avec un grand référencement sont pratiquement impossibles jusqu'à ce que google mette à jour leurs crawlers?


Reproduit sur webmasters à la suite des commentaires de John Conde.

27
demandé sur Community 2014-04-13 17:30:04

5 réponses

voici un aperçu complet de la façon de rendre votre application conviviale sur un service de stockage tel que S3, avec de belles urls (no #) et tout avec grunt avec la commande simple à effectuer après la construction:

grunt seo

c'est toujours un puzzle de solutions, mais ça marche et c'est le mieux que vous puissiez faire. Merci à @ericluwj et à son blogpost qui m'a inspiré.

vue d'ensemble

L'objectif et de la structure de l'url

le but est de créer 1 fichier html par état dans votre application angulaire. La seule hypothèse majeure est que vous supprimez le ' # ' de votre url en utilisant html5history (ce que vous devriez faire !) et que tous vos chemins sont absolus ou utilisant des États angulaires. Il y a beaucoup de billets expliquant comment le faire.

Url terminer par une barre oblique comme ceci http://yourdomain.com/page1 /

Personnellement, j'ai fait en sorte que http://yourdomain.com/page1 ( no slash arrière) atteint également sa destination, mais c'est hors sujet ici. J'ai aussi fait en sorte que chaque langue ait un état différent et une url différente.

Le SEO logique

notre objectif est que lorsque quelqu'un atteint votre site Web par le biais d'une demande http:

  • si c'est un moteur de recherche crawler: gardez-le sur la page qui contient le html requis. La page contient également la logique angulaire (par exemple pour démarrer votre application), mais le crawler ne peut pas lire cela, il est donc intentionnellement coincé avec le html que vous lui avez servi et va indexer cela.
  • pour les humains normaux et les machines intelligentes: assurez-vous angular est activé, effacer le html généré et démarrer votre application normalement

Le grognement des tâches

Ici, nous allons avec le grognement des tâches:

  //grunt plugins you will need:
  grunt.loadNpmTasks('grunt-prerender');
  grunt.loadNpmTasks('grunt-replace');
  grunt.loadNpmTasks('grunt-wait');
  grunt.loadNpmTasks('grunt-aws-s3');

  //The grunt tasks in the right order
  grunt.registerTask('seo', 'First launch server, then prerender and replace', function (target) {
    grunt.task.run([
      'concurrent:seo' //Step 1: in parrallel launch server, then perform so-called seotasks
    ]);
  });

  grunt.registerTask('seotasks', [
    'http', //This is an API call to get all pages on my website. Skipping this step in this tutorial.
    'wait', // wait 1.5 sec to make sure that server is launched
    'prerender', //Step 2: create a snapshot of your website
    'replace', //Step 3: clean the mess
    'sitemap', //Create a sitemap of your production environment
    'aws_s3:dev' //Step 4: upload
  ]);

Étape 1: Lancement du serveur local avec concurrent: seo

nous devons d'abord lancer un serveur local (comme Grunt serve) afin que nous puissions prendre des instantanés de notre site web.

//grunt config
concurrent: {
  seo: [
    'connect:dist:keepalive', //Launching a server and keeping it alive
    'seotasks' //now that we have a running server we can launch the SEO tasks
  ]
}

Étape 2: Créez un instantané de votre site Web avec grunt prerender

les plugins grunt-prerender vous permettent de prendre un instantané de n'importe quel site Web en utilisant PhantomJS. Dans notre cas, nous voulons prendre un instantané de toutes les pages du localhost site internet, nous venons de lancer.

//grunt config
prerender: {
  options: {
    sitePath: 'http://localhost:9001', //points to the url of the server you just launched. You can also make it point to your production website.
    //As you can see the source urls allow for multiple languages provided you have different states for different languages (see note below for that)
    urls: ['/', '/projects/', '/portal/','/en/', '/projects/en/', '/portal/en/','/fr/', '/projects/fr/', '/portal/fr/'],//this var can be dynamically updated, which is done in my case in the callback of the http task
    hashed: true,
    dest: 'dist/SEO/',//where your static html files will be stored
    timeout:5000,
    interval:5000, //taking a snapshot of how the page looks like after 5 seconds.
    phantomScript:'basic',
    limit:7 //# pages processed simultaneously 
  }
}

Étape 3: Nettoyer le mess avec grunt remplacer

si vous ouvrez les fichiers rendus, ils fonctionneront pour les crawlers, mais pas pour les humains. Pour les humains qui utilisent chrome, vos directives se chargeront deux fois. Par conséquent, vous devez rediriger les navigateurs intelligents vers votre page d'accueil avant angular obtient activé (i.e., juste après la tête).

//Add the script tag to redirect if we're not a search bot
replace: {
  dist: {
    options: {
      patterns: [
        {
          match: '<head>',
          //redirect to a clean page if not a bot (to your index.html at the root basically).
          replacement: '<head><script>if(!/bot|googlebot|crawler|spider|robot|crawling/i.test(navigator.userAgent)) { document.location = "/#" + window.location.pathname; }</script>'
          //note: your hashbang (#) will still work.
        }
      ],
      usePrefix: false
    },
    files: [
      {expand: true, flatten: false, src: ['dist/SEO/*/**/*.html'], dest: ''} 
    ]
  }

assurez-vous également d'avoir ce code dans votre index.html sur votre élément ui-view, qui efface toutes les directives html générées avant le démarrage angulaire.

<div ui-view autoscroll="true" id="ui-view"></div>

<!-- this script is needed to clear ui-view BEFORE angular starts to remove the static html that has been generated for search engines who cannot read angular -->
<script> 
  if(!/bot|googlebot|crawler|spider|robot|crawling/i.test( navigator.userAgent)) { document.getElementById('ui-view').innerHTML = ""; }
</script>

Étape 4: Télécharger aws

vous téléchargez d'abord votre dossier dist qui contient votre build. Ensuite, vous l'écrasez avec les fichiers que vous avez créés et mis à jour.

aws_s3: {
  options: {
    accessKeyId: "<%= aws.accessKeyId %>", // Use the variables
    secretAccessKey: "<%= aws.secret %>", // You can also use env variables
    region: 'eu-west-1',
    uploadConcurrency: 5, // 5 simultaneous uploads
  },
  dev: {
    options: {
      bucket: 'xxxxxxxx'
    },
    files: [
      {expand: true, cwd: 'dist/', src: ['**'], exclude: 'SEO/**', dest: '', differential: true},
      {expand: true, cwd: 'dist/SEO/', src: ['**'], dest: '', differential: true},
    ]
  }
}

C'est ça, vous avez votre solution ! Les humains et les robots seront en mesure de lire votre Web-app

2
répondu Robycool 2017-05-09 07:44:09

en fait c'est une tâche qui est en effet très pénible, mais j'ai réussi à faire fonctionner SEO bien avec mon site spa AngularJS (hébergé sur AWS S3) à http://www.jobbies.co / . L'idée principale est de pré-générer et de peupler le contenu dans le HTML. Les modèles seront toujours chargés lorsque la page se chargera et le contenu pré-rendu sera remplacé.

vous pouvez en savoir plus sur ma solution à http://www.ericluwj.com/2015/11/17/seo-for-angularjs-on-s3.html , mais notez qu'il y a beaucoup de conditions.

3
répondu ericluwj 2015-11-26 09:55:05

si vous utilisez ng-cape de manière intéressante, il pourrait être une bonne solution.

je n'ai pas essayé moi-même, mais il faut travailler dans la théorie

la solution dépend fortement de CSS, mais elle devrait parfaitement bien. Par exemple, vous avez trois états dans votre application angulaire: - index (chemin d'accès : #/) - sur (chemin d'accès : #/sur) - contact (chemin d'accès : #/contact)

le cas de base pour index peut être ajouté dans aussi, mais sera difficile donc je vais le laisser pour l'instant.

Faites votre HTML ressembler à ceci:

<body>
    <div ng-app="myApp" ng-cloak>
        <!-- Your whole angular app goes here... -->
    </div>
    <div class="static">
        <div id="about class="static-other">
            <!-- Your whole about content here... -->
        </div>
        <div id="contact" class="static-other">
            <!-- Your whole contact content here... -->
        </div>
        <div id="index" class="static-main">
            <!-- Your whole index content here... -->
        </div>
    </div>
</body>

(il est Important que vous mettiez votre cas index dernier, si vous voulez le rendre plus impressionnant)

Ensuite, faites que votre CSS ressemble à quelque chose comme ceci: -

[ng-cloak], .static { display: none; }
[ng-cloak] ~ .static { display: block; }

C'est juste que ça marchera probablement assez bien pour vous de toute façon. La directive mg-cloak gardera votre application angulaire cachée lorsque l'application n'est pas chargée et affichera votre contenu statique à la place. Google obtiendra votre contenu statique dans le HTML. En bonus, les utilisateurs finaux peuvent également voir bien les styles de contenu statique tout en charges angulaires.

vous pouvez alors devenir plus créatif si vous commencez à utiliser :cible pseudo-sélecteurs dans votre CSS. Vous pouvez utiliser des liens réels dans votre contenu statique, mais il suffit de leur faire des liens vers diverses ids. Donc dans # index div assurez-vous que vous avez des liens vers #about et #contact. Remarque manquant '/' dans les liens. Les id HTML ne peuvent pas commencer avec un oblique.

alors faites votre CSS ressembler à ceci:

[ng-cloak], .static { display: none; }
[ng-cloak] ~ .static { display: block; }
.static-other {display: none;}
.static-other:target {display: block;}
.static-other:target ~ .static-main {display: none;}

vous avez maintenant une application statique de fonctionnement complet avec routage qui fonctionne avant le démarrage angulaire.

comme bonus supplémentaire, quand angular démarre, il est assez intelligent pour convertir #about En #/about automatiquement, et l'expérience ne devrait même pas se casser du tout.

aussi, pour ne pas oublier le problème de SEO a été totalement résolu, bien sûr. Je n'ai pas j'ai utilisé cette technique, car j'ai toujours eu un serveur à configurer, mais je suis très intéressé par la façon dont cela fonctionne pour vous.

Espérons que cette aide.

2
répondu Naman Goel 2014-05-12 04:55:54

comme AWS offre Lambda@Edge comme un service, nous pouvons traiter cette question sans grunt ou autre chose. (Au moins pour un truc de base)

j'ai essayé Lambda@Edge et cela a fonctionné comme prévu, dans mon cas j'ai juste eu toutes les routes définies à " / " dans Lambda@Edge (sauf pour les fichiers sont présents dans s3 comme css, images etc).

l'événement pour la Lambda que j'ai défini est" viewerRequest " et suit est le code.

'use strict';

exports.handler = (event, context, callback) => {
    console.log("Event received is", JSON.stringify(event));
    console.log("Context received is", context);
    const request = event.Records[0].cf.request;
    if (request.uri.endsWith(".rt")) {
        console.log("URI is matching with .rt, the URI is ", request.uri);
        request.uri = "/";
    } else {
        console.log("URI is not ending with rt so letting it go URI is", request.uri);
    }
    console.log("Final request URI is", request.uri);
    callback(null, request);
};

Il n'est pas difficile de vérifier les diagraphies dans la couverture nuageuse, car les diagraphies sont peuplées dans la région de la couverture nuageuse qui est plus près de l'emplacement de bord qui traite la demande.

pour ex. Bien que cette Lambda soit déployée/écrite pour us-east je le vois dans la région ap-south alors que j'accède au front nuageux depuis Singapour. Coché dans les outils Google webmaster 'Fetch as google' options et la page est rendue et vue comme prévu.

2
répondu Neo 2017-08-29 05:39:11

ça fait des jours que je cherche une solution. Pour autant que je sache, il n'y a pas de bonne solution au problème. J'espère que firebase activera éventuellement les redirections utilisateur-agent. Si vous avez l'argent, vous pouvez utiliser MaxCDN enterprise. Ils offrent des règles de bord qui comprend des redirections par l'agent utilisateur .

https://www.maxcdn.com/features/rules /

0
répondu Boedy 2016-01-06 21:48:44