Sites Web JS et référencement «à une seule page»

128

De nos jours, il existe de nombreux outils intéressants pour créer de puissants sites Web JavaScript d'une seule page. À 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 tous les éléments de la 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 entre sur le site Web, laissez le serveur rendre la page exactement comme le client le ferait lors de la navigation. Donc, si je vais http://example.com/my_pathdirectement, le serveur rendra la même chose que le client si je passe /my_pathpar 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_pathle serveur, il devrait lui donner une version JavaScript lourde du site Web. Mais si le robot Google visite, le serveur devrait lui donner un minimum de HTML avec le contenu que je souhaite que Google indexe.

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

Je pense avoir vu la deuxième solution pour certains 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.

Donc, ce que je me demande vraiment, c'est 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 régulier, serais-je alors puni dans les résultats de recherche?
user544941
la source

Réponses:

44

Bien que le n ° 2 puisse ê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 un 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é (pas seulement pour les personnes handicapées, mais aussi 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.-à-d. être consultés, consultés, lus, traités ou autrement utilisés) à tous ces différents navigateurs. Un lecteur d'écran, un robot d'exploration de moteur de recherche ou un utilisateur avec JavaScript activé, devraient tous être capables d'utiliser / indexer / comprendre les fonctionnalités de base de votre site sans problème.

pushStaten'ajoute pas à ce fardeau, d'après mon expérience. Cela ne fait qu'apporter ce qui était autrefois une réflexion après coup et "si nous en avons le temps" au premier plan du développement Web.

Ce que vous décrivez dans l'option n ° 1 est généralement la meilleure façon de procéder - mais, comme d'autres problèmes d'accessibilité et de référencement, le faire pushStatedans une application contenant beaucoup de JavaScript nécessite une planification préalable , sinon cela deviendra un fardeau important. 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 pushStaterécemment travaillé avec et SEO pour quelques applications différentes, et j'ai trouvé ce que je pense être une bonne approche. Il suit essentiellement votre élément n ° 1, mais tient compte de la non duplication du HTML / des 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 utiliser, ainsi que pour mes spécifications JavaScript Jasmine. Cela supprime la duplication du balisage entre le côté serveur et le côté client.

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

Par exemple, je crée une application de galerie d'images avec pushState. Si vous demandez /images/1au serveur, il rendra toute la galerie d'images sur le serveur et enverra tout le HTML, CSS et JavaScript à votre navigateur. Si vous avez désactivé JavaScript, cela fonctionnera parfaitement. Chaque action que vous entreprenez demandera une URL différente au serveur et le serveur rendra tout le balisage pour votre navigateur. Si vous avez activé JavaScript, cependant, le JavaScript récupérera le 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")
  });
});

C'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 rendu par le serveur, à l'instance de vue comme elpour la vue. Je n'appelle pas render ou je ne demande pas à la vue de générer un elpour moi, lorsque la première vue est chargée. J'ai une méthode de rendu disponible 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 j'en ai besoin.

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

Éditer

Prenons un exemple plus complexe, où vous avez une liste qui doit être jointe (à partir des commentaires ci-dessous)

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

<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>éléments. Avec l'utilisation de l' data-idattribut, vous pouvez trouver facilement le modèle dont 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 UserListViewparcourt toutes les <li>balises et attache 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 rendu par le serveur et faire prendre en charge mon JavaScript et l'exécuter, est un excellent moyen de faire avancer les choses pour le référencement, l'accessibilité et le pushStatesupport.

J'espère que cela pourra aider.

Derick Bailey
la source
Je comprends votre point de vue, mais ce qui est intéressant, c'est la façon dont le rendu est effectué après "votre JavaScript prend le dessus". Dans un exemple plus compliqué, vous devrez peut-être utiliser un modèle non compilé sur le client, parcourant en boucle un tableau d'utilisateurs pour créer une liste. La vue est restituée chaque fois que le modèle d'un utilisateur change. Comment feriez-vous cela sans dupliquer les modèles (et ne pas demander au serveur de rendre la vue pour le client)?
user544941
les 2 articles de blog que j'ai liés devraient collectivement vous montrer comment avoir des modèles qui peuvent être utilisés sur le client et le serveur - aucune duplication nécessaire. le serveur devra rendre la page entière si vous voulez qu'elle soit accessible et conviviale pour le référencement. J'ai mis à jour ma réponse pour inclure un exemple plus complexe d'attachement à une liste d'utilisateurs qui a été rendue par le serveur
Derick Bailey
22

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 les choses deux fois. (Tant que votre application est entièrement contrôlable via des fragments d'ancrage.)

Ariel
la source
En fait, ce n'est pas ce que je recherche. Ce sont quelques variantes de la première solution et comme je l'ai mentionné, je ne suis pas très satisfait de cette approche.
user544941
2
Vous n'avez pas lu toute ma réponse. Vous utilisez également un backend spécial qui rend le javascript pour vous - vous n'écrivez pas les choses deux fois.
Ariel le
Oui, j'ai lu cela. Mais si je vous avais bien compris, ce serait un sacré programme, car il devrait simuler chaque action qui déclenche le pushState. Alternativement, je pourrais lui donner les actions directement, mais alors nous ne sommes plus si SEC.
user544941
2
Je pense que c'est essentiellement un navigateur sans façade. Mais, oui, vous devez rendre le programme complètement contrôlable à partir de fragments d'ancrage. Vous devez également vous assurer que tous les liens contiennent le fragment approprié, avec ou à la place de onClicks.
Ariel le
17

Donc, il semble que la principale préoccupation est d'être 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/1595913 ).
  • (testez la validité du lien en convertissant le lien en votre schéma d'URL et testez par rapport à l'existence du 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 traitez 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 afficher le HTML et le javascript sur le serveur et envoyer le google bot le html résultant. Au fur et à mesure que le bot analyse le code HTML, il peut atteindre vos autres liens "pushState" / une page sur le serveur <a href="https://stackoverflow.com/someotherpage">mylink</a>, le serveur réécrit l'URL dans votre fichier d'application, la 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/1595913 )
  • Pour éviter toute confusion avec les 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 capable d'analyser les <a>balises. Dans ce cas, la gestion de 404 est plus facile puisque vous pouvez simplement vérifier l'existence du fichier statique avec un nom contenant le chemin de l'url.
  • Si tu utilises #! hash bang syntax pour vos liens de site, un scénario similaire s'applique, sauf que le moteur de serveur de réécriture d'url rechercherait _escaped_fragment_ dans l'url et formaterait l'URL dans 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 utilisant phantom.js pour le référencement:

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

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

Léonidaz
la source
4

Si vous utilisez Rails, essayez poirot . C'est un bijou qui rend très simple la réutilisation des modèles de moustache ou de guidon 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 dans votre tête pour une utilisation côté client:

<%= template_include_tag 'some_thingy' %>

Rendre côté client:

html = poirot.someThingy(my_model)
Tim Scott
la source
3

Pour prendre un angle légèrement différent, votre deuxième solution serait la bonne en termes d' accessibilité ... vous fourniriez un contenu alternatif aux utilisateurs qui ne peuvent pas utiliser javascript (ceux qui ont 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.

Clive
la source
Et est-ce que quelqu'un vous a prouvé que vous aviez tort? Cela fait longtemps que le commentaire a été publié
jkulak
1

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

J'étais en fait davantage penché 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 devrait lui fournir une version JavaScript lourde du site Web. Mais si le robot Google visite, le serveur devrait lui donner un minimum de HTML avec le contenu que je souhaite que Google indexe.

Voici mon point de vue sur la résolution du problème. Bien que son efficacité ne soit pas confirmée, elle peut 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 est Ruby on Rails. Vous avez un site de blog simple et vous aimeriez que les moteurs de recherche indexent tous vos articles indexet showpages.

Disons que vos itinéraires sont configurés comme ceci:

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

Assurez-vous que chaque contrôleur côté serveur restitue le même modèle que celui dont votre infrastructure côté client a besoin pour s'exécuter (html / css / javascript / etc). Si aucun des contrôleurs ne correspond à la demande (dans cet exemple, nous n'avons qu'un ensemble d'actions RESTful pour le ArticlesController), alors faites correspondre tout le reste et restituer simplement le modèle et laisser le framework côté client gérer le routage. La seule différence entre frapper un contrôleur et frapper la correspondance générique serait la possibilité de rendre le contenu en fonction de 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 un contenu qui n'est pas visible pour les navigateurs. Ainsi, 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 divnœud que vous display: noneen 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é par JavaScript ouvre la page:

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

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

Mais, lorsqu'un utilisateur visite la même page et que JavaScript est activé, le #no-jsnœud sera supprimé pour 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.

Michael van Rooijen
la source
1
Eh bien, si vous affichez le contenu pour la première fois et que vous le supprimez un peu plus tard, l'utilisateur final peut très probablement remarquer que le contenu clignote / scintille dans son navigateur :) Surtout s'il s'agit d'un navigateur lent, d'une taille énorme de contenu HTML que vous essayez d'afficher / supprimer et certains délai avant le chargement et l'exécution de votre code JS. Ce que tu penses?
Evereq
1

Utilisez NodeJS côté serveur, naviguez votre code côté client et acheminez chaque uri de requête http (à l'exception des ressources http statiques) via un client côté serveur pour fournir le premier 'bootsnap' (un instantané de la page dans son état). Utilisez quelque chose comme jsdom pour gérer les dom-ops jquery sur le serveur. Après le retour du bootnap, 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 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/

Recherche de phrases
la source
0

Utilisez le modèle de fermeture Google pour afficher les pages. Il se compile en javascript ou en 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 peuvent être effectuées contre l'API pour minimiser le trafic.

Aleš Kotnik
la source