web-dev-qa-db-fra.com

Sites Web et référencement JS "monopage"

Il existe de nombreux outils sympas pour créer de puissants sites Web JavaScript "à une seule page" de nos jours. À mon avis, cela se fait correctement en laissant le serveur agir comme une API (et rien de plus) et en laissant le client gérer toutes les choses de génération HTML. Le problème avec ce "modèle" est le manque de support des moteurs de recherche. Je peux penser à deux solutions:

  1. Lorsque l'utilisateur accède au site Web, laissez le serveur afficher la page exactement comme le ferait le client lors de la navigation. Donc, si je vais à http://example.com/my_path directement le serveur rendrait la même chose que le client si je vais à /my_path via pushState.
  2. Laissez le serveur fournir un site Web spécial uniquement pour les robots des moteurs de recherche. Si un utilisateur normal visite http://example.com/my_path le serveur doit lui fournir une version JavaScript lourde du site. Mais si le robot Google visite, le serveur doit lui donner un minimum de code HTML avec le contenu que je veux que Google indexe.

La première solution est discutée plus loin ici . J'ai travaillé sur un site Web en faisant cela et ce n'est pas une expérience très agréable. Ce n'est pas DRY et dans mon cas, j'ai dû utiliser deux moteurs de modèle différents pour le client et le serveur.

Je pense avoir vu la deuxième solution pour quelques bons vieux sites Web Flash. J'aime cette approche beaucoup plus que la première et avec le bon outil sur le serveur, cela pourrait être fait sans douleur.

Je me demande donc vraiment ce qui suit:

  • Pouvez-vous penser à une meilleure solution?
  • Quels sont les inconvénients de la deuxième solution? Si Google découvre d'une manière ou d'une autre que je ne diffuse pas exactement le même contenu pour le robot Google en tant qu'utilisateur normal, serais-je alors puni dans les résultats de recherche?
128
user544941

Alors que # 2 pourrait être "plus facile" pour vous en tant que développeur, il ne fournit que l'exploration des moteurs de recherche. Et oui, si Google découvre que vous diffusez du contenu différent, vous pourriez être pénalisé (je ne suis pas un expert en la matière, mais j'en ai entendu parler).

Le référencement et l'accessibilité (non seulement pour les personnes handicapées, mais l'accessibilité via des appareils mobiles, des appareils à écran tactile et d'autres plates-formes informatiques/Internet non standard) ont tous deux une philosophie sous-jacente similaire: un balisage sémantiquement riche qui est "accessible" (c'est-à-dire peut accessibles, consultés, lus, traités ou autrement utilisés) à tous ces différents navigateurs. Un lecteur d'écran, un robot de recherche ou un utilisateur avec JavaScript activé, devraient tous être en mesure d'utiliser/indexer/comprendre les fonctionnalités de base de votre site sans problème.

pushState n'ajoute pas à ce fardeau, d'après mon expérience. Il n'apporte que ce qui était autrefois une réflexion après coup et "si nous avons le temps" au premier plan du développement Web.

Ce que vous décrivez dans l'option # 1 est généralement la meilleure façon de procéder - mais, comme pour les autres problèmes d'accessibilité et de référencement, le faire avec pushState dans une application JavaScript nécessite une planification préalable ou cela deviendra important charge. Il doit être intégré à la page et à l'architecture de l'application dès le début - la mise à niveau est douloureuse et entraînera plus de duplication que nécessaire.

J'ai récemment travaillé avec pushState et SEO pour différentes applications, et j'ai trouvé ce que je pense être une bonne approche. Il suit fondamentalement votre article n ° 1, mais tient compte de la duplication de html/modèles.

La plupart des informations se trouvent dans ces deux articles de blog:

http://lostechies.com/derickbailey/2011/09/06/test-driving-backbone-views-with-jquery-templates-the-jasmine-gem-and-jasmine-jquery/

et

http://lostechies.com/derickbailey/2011/06/22/rendering-a-Rails-partial-as-a-jquery-template/

L'essentiel est que j'utilise des modèles ERB ou HAML (exécutant Ruby on Rails, Sinatra, etc.) pour mon rendu côté serveur et pour créer les modèles côté client que Backbone peut également utiliser. comme pour mes spécifications Jasmine JavaScript. Cela évite la duplication du balisage entre le côté serveur et le côté client.

À partir de là, vous devez prendre quelques mesures supplémentaires pour que votre JavaScript fonctionne avec le code HTML rendu par le serveur - véritable amélioration progressive; prendre le balisage sémantique livré et l'améliorer avec JavaScript.

Par exemple, je crée une application de galerie d'images avec pushState. Si vous demandez /images/1 depuis le serveur, il restituera l'intégralité de la galerie d'images sur le serveur et enverra tout le HTML, CSS et JavaScript vers votre navigateur. Si JavaScript est désactivé, cela fonctionnera parfaitement. Chaque action que vous entreprendrez demandera une URL différente au serveur et le serveur restituera tout le balisage pour votre navigateur. Si vous avez activé JavaScript, le JavaScript récupérera le code HTML déjà rendu avec quelques variables générées par le serveur et prendra le relais à partir de là.

Voici un exemple:

<form id="foo">
  Name: <input id="name"><button id="say">Say My Name!</button>
</form>

Une fois que le serveur a rendu cela, le JavaScript le récupère (en utilisant une vue Backbone.js dans cet exemple)

FooView = Backbone.View.extend({
  events: {
    "change #name": "setName",
    "click #say": "sayName"
  },

  setName: function(e){
    var name = $(e.currentTarget).val();
    this.model.set({name: name});
  },

  sayName: function(e){
    e.preventDefault();
    var name = this.model.get("name");
    alert("Hello " + name);
  },

  render: function(){
    // do some rendering here, for when this is just running JavaScript
  }
});

$(function(){
  var model = new MyModel();
  var view = new FooView({
    model: model,
    el: $("#foo")
  });
});

Ceci est un exemple très simple, mais je pense qu'il fait passer le message.

Lorsque j'instante la vue après le chargement de la page, je fournis le contenu existant du formulaire qui a été rendu par le serveur, à l'instance de vue en tant que el pour la vue. Je suis pas appelant render ou que la vue génère un el pour moi, lorsque la première vue est chargée. J'ai une méthode de rendu disponible pour une fois que la vue est opérationnelle et que la page est entièrement en JavaScript. Cela me permet de restituer la vue plus tard si nécessaire.

Cliquer sur le bouton "Dites mon nom" avec JavaScript activé provoquera une boîte d'alerte. Sans JavaScript, il reviendrait sur le serveur et le serveur pourrait rendre le nom à un élément html quelque part.

Modifier

Prenons un exemple plus complexe, où vous avez une liste à joindre (à partir des commentaires ci-dessous)

Supposons que vous ayez une liste d'utilisateurs dans un <ul> tag. Cette liste a été rendue par le serveur lorsque le navigateur a fait une demande, et le résultat ressemble à ceci:

<ul id="user-list">
  <li data-id="1">Bob
  <li data-id="2">Mary
  <li data-id="3">Frank
  <li data-id="4">Jane
</ul>

Vous devez maintenant parcourir cette liste et attacher une vue et un modèle Backbone à chacun des <li> articles. Avec l'utilisation du data-id, vous pouvez trouver facilement le modèle d'où provient chaque balise. Vous aurez alors besoin d'une vue de collection et d'une vue d'élément suffisamment intelligentes pour s'attacher à ce code html.

UserListView = Backbone.View.extend({
  attach: function(){
    this.el = $("#user-list");
    this.$("li").each(function(index){
      var userEl = $(this);
      var id = userEl.attr("data-id");
      var user = this.collection.get(id);
      new UserView({
        model: user,
        el: userEl
      });
    });
  }
});

UserView = Backbone.View.extend({
  initialize: function(){
    this.model.bind("change:name", this.updateName, this);
  },

  updateName: function(model, val){
    this.el.text(val);
  }
});

var userData = {...};
var userList = new UserCollection(userData);
var userListView = new UserListView({collection: userList});
userListView.attach();

Dans cet exemple, le UserListView parcourra tous les <li> tags et attachez un objet de vue avec le modèle correct pour chacun. il configure un gestionnaire d'événements pour l'événement de changement de nom du modèle et met à jour le texte affiché de l'élément lorsqu'une modification se produit.


Ce type de processus, pour prendre le HTML que le serveur a rendu et pour que mon JavaScript le prenne en charge et l'exécute, est un excellent moyen de faire avancer les choses pour le référencement, l'accessibilité et la prise en charge de pushState.

J'espère que ça t'as aidé.

44
Derick Bailey

Je pense que vous en avez besoin: http://code.google.com/web/ajaxcrawling/

Vous pouvez également installer un backend spécial qui "rend" votre page en exécutant javascript sur le serveur, puis le sert à Google.

Combinez les deux choses et vous avez une solution sans programmer deux fois les choses. (Tant que votre application est entièrement contrôlable via des fragments d'ancrage.)

22
Ariel

Il semble donc que la principale préoccupation soit le SEC

  • Si vous utilisez pushState, demandez à votre serveur d'envoyer le même code exact pour toutes les URL (qui ne contiennent pas d'extension de fichier pour servir les images, etc.) "/ mydir/myfile", "/ myotherdir/myotherfile" ou root "/ "- toutes les demandes reçoivent le même code exact. Vous devez avoir un moteur de réécriture d'URL. Vous pouvez également servir un tout petit peu de HTML et le reste peut provenir de votre CDN (en utilisant require.js pour gérer les dépendances - voir https://stackoverflow.com/a/13813102/159591 ).
  • (testez la validité du lien en convertissant le lien vers votre schéma d'URL et testez-le par rapport à l'existence de contenu en interrogeant une source statique ou dynamique. s'il n'est pas valide, envoyez une réponse 404.)
  • Lorsque la demande ne provient pas d'un robot Google, vous procédez simplement normalement.
  • Si la demande provient d'un robot Google, vous utilisez phantom.js - navigateur webkit sans tête ( "Un navigateur sans tête est simplement un navigateur web complet sans interface visuelle." ) pour rendre le HTML et le Javascript sur le serveur et envoyer au robot Google le HTML résultant. Lorsque le bot analyse le code HTML, il peut frapper vos autres liens/certaines pages "pushState" sur le serveur <a href="/someotherpage">mylink</a>, le serveur réécrit l'url dans votre fichier d'application, le charge dans phantom.js et le code HTML résultant est envoyé au bot, etc.
  • Pour votre html, je suppose que vous utilisez des liens normaux avec une sorte de piratage (par exemple, en utilisant avec backbone.js https://stackoverflow.com/a/9331734/159591 )
  • Pour éviter toute confusion avec des liens, séparez votre code API qui sert json dans un sous-domaine séparé, par exemple api.mysite.com
  • Pour améliorer les performances, vous pouvez prétraiter les pages de votre site pour les moteurs de recherche à l'avance pendant les heures creuses en créant des versions statiques des pages en utilisant le même mécanisme avec phantom.js et par conséquent servir les pages statiques aux robots Google. Le prétraitement peut être effectué avec une application simple qui peut analyser <a> Mots clés. Dans ce cas, la gestion de 404 est plus facile car vous pouvez simplement vérifier l'existence du fichier statique avec un nom qui contient le chemin de l'url.
  • Si tu utilises #! hash bang syntax pour les liens de votre site, un scénario similaire s'applique, sauf que le moteur du serveur de réécriture d'URL rechercherait _escaped_fragment_ dans l'URL et formaterait l'URL selon votre schéma d'URL.
  • Il existe quelques intégrations de node.js avec phantom.js sur github et vous pouvez utiliser node.js comme serveur Web pour produire une sortie html.

Voici quelques exemples d'utilisation de phantom.js pour seo:

http://backbonetutorials.com/seo-for-single-page-apps/

http://thedigitalself.com/blog/seo-and-javascript-with-phantomjs-server-side-rendering

17
Leonidaz

Si vous utilisez Rails, essayez poirot . C'est un joyau qui le rend très simple à réutiliser moustache ou guidon modèles côté client et serveur.

Créez un fichier dans vos vues comme _some_thingy.html.mustache.

Rendu côté serveur:

<%= render :partial => 'some_thingy', object: my_model %>

Mettez le modèle votre tête pour une utilisation côté client:

<%= template_include_tag 'some_thingy' %>

Côté client Rendre:

html = poirot.someThingy(my_model)
4
Tim Scott

Pour prendre un angle légèrement différent, votre deuxième solution serait la bonne en termes de accessibilité ... vous fourniriez un contenu alternatif aux utilisateurs qui ne peuvent pas utiliser javascript (ceux avec des lecteurs d'écran, etc.) .

Cela ajouterait automatiquement les avantages du référencement et, à mon avis, ne serait pas considéré comme une technique "coquine" par Google.

3
Clive

Intéressant. J'ai cherché des solutions viables, mais cela semble assez problématique.

En fait, je me penchais davantage vers votre deuxième approche:

Laissez le serveur fournir un site Web spécial uniquement pour les robots des moteurs de recherche. Si un utilisateur normal visite http://example.com/my_path le serveur doit lui donner une version lourde JavaScript du site Web. Mais si le robot Google visite, le serveur doit lui donner un minimum de code HTML avec le contenu que je veux que Google indexe.

Voici mon point de vue sur la résolution du problème. Bien qu'il ne soit pas confirmé qu'il fonctionne, il pourrait fournir des informations ou des idées à d'autres développeurs.

Supposons que vous utilisez un framework JS qui prend en charge la fonctionnalité "Push state" et que votre framework backend soit Ruby on Rails. Vous avez un site de blog simple et vous souhaitez que les moteurs de recherche indexent tous vos article index et show pages.

Supposons que vos itinéraires soient configurés comme suit:

resources :articles
match "*path", "main#index"

Assurez-vous que chaque contrôleur côté serveur affiche le même modèle que votre infrastructure côté client doit exécuter (html/css/javascript/etc). Si aucun des contrôleurs ne correspond dans la demande (dans cet exemple, nous n'avons qu'un ensemble d'actions RESTful pour le ArticlesController), alors faites simplement correspondre n'importe quoi d'autre et rendez simplement le modèle et laissez le framework côté client gérer le routage. La seule différence entre frapper un contrôleur et frapper l'adaptateur générique serait la possibilité de rendre le contenu basé sur l'URL qui a été demandée aux appareils désactivés par JavaScript.

D'après ce que je comprends, c'est une mauvaise idée de rendre le contenu qui n'est pas visible pour les navigateurs. Donc, lorsque Google l'indexe, les gens passent par Google pour visiter une page donnée et il n'y a pas de contenu, alors vous allez probablement être pénalisé. Ce qui me vient à l'esprit, c'est que vous rendez le contenu dans un nœud div que vous display: none en CSS.

Cependant, je suis presque sûr que cela n'a pas d'importance si vous faites simplement ceci:

<div id="no-js">
  <h1><%= @article.title %></h1>
  <p><%= @article.description %></p>
  <p><%= @article.content %></p>
</div>

Et puis en utilisant JavaScript, qui ne s'exécute pas lorsqu'un appareil désactivé JavaScript ouvre la page:

$("#no-js").remove() # jQuery

De cette façon, pour Google et pour toute personne disposant d'appareils désactivés par JavaScript, ils verraient le contenu brut/statique. Le contenu est donc physiquement présent et visible par toute personne disposant d'appareils désactivés par JavaScript.

Mais, lorsqu'un utilisateur visite la même page et que a JavaScript activé, le #no-js le nœud sera supprimé afin de ne pas encombrer votre application. Ensuite, votre infrastructure côté client traitera la demande via son routeur et affichera ce qu'un utilisateur devrait voir lorsque JavaScript est activé.

Je pense que cela pourrait être une technique valide et assez facile à utiliser. Bien que cela puisse dépendre de la complexité de votre site Web/application.

Cependant, veuillez me corriger si ce n'est pas le cas. Je pensais juste partager mes pensées.

1
Michael van Rooijen

Utilisez NodeJS côté serveur, naviguez votre code côté client et acheminez l'URI de chaque demande http (sauf pour les ressources http statiques) via un client côté serveur pour fournir le premier `` bootsnap '' (un instantané de la page où il se trouve). Utilisez quelque chose comme jsdom pour gérer les opérations dom jquery sur le serveur. Une fois le démarrage effectué, configurez la connexion Websocket. Il est probablement préférable de faire la différence entre un client WebSocket et un client côté serveur en établissant une sorte de connexion wrapper sur le côté client (le client côté serveur peut communiquer directement avec le serveur). J'ai travaillé sur quelque chose comme ça: https://github.com/jvanveen/rnet/

1
Phrearch

Utilisez Google Closure Template pour afficher les pages. Il se compile en javascript ou Java, il est donc facile de rendre la page côté client ou côté serveur. Lors de la première rencontre avec chaque client, rendez le html et ajoutez javascript comme lien dans l'en-tête. Le robot lira uniquement le code HTML, mais le navigateur exécutera votre script. Toutes les demandes ultérieures du navigateur pourraient être effectuées contre l'api pour minimiser le trafic.

0
Aleš Kotnik