desarrollo-web-br-bd.com

Sitios web de JS de "una sola página" y SEO

Hay una gran cantidad de herramientas geniales para hacer sitios web de JavaScript de "página única" poderosos en la actualidad. En mi opinión, esto se hace correctamente dejando que el servidor actúe como una API (y nada más) y dejando que el cliente maneje todo el material de generación de HTML. El problema con este "patrón" es la falta de soporte del motor de búsqueda. Puedo pensar en dos soluciones:

  1. Cuando el usuario ingresa al sitio web, deje que el servidor rinda la página exactamente como lo haría el cliente al navegar. Entonces, si voy a http://example.com/my_path directamente, el servidor representará lo mismo que el cliente si voy a /my_path a través de pushState.
  2. Permita que el servidor proporcione un sitio web especial solo para los robots de los motores de búsqueda. Si un usuario normal visita http://example.com/my_path, el servidor debe darle una versión pesada de JavaScript del sitio web. Pero si el bot de Google visita, el servidor debería darle un mínimo de HTML con el contenido que quiero que indexe Google.

La primera solución se discute más adelante aquí . He estado trabajando en un sitio web haciendo esto y no es una experiencia muy agradable. No es DRY y en mi caso tuve que usar dos motores de plantillas diferentes para el cliente y el servidor.

Creo que he visto la segunda solución para algunos buenos sitios web de Flash. Me gusta este enfoque mucho más que el primero y, con la herramienta adecuada en el servidor, se puede hacer sin problemas.

Entonces, lo que realmente me pregunto es lo siguiente:

  • ¿Se te ocurre alguna solución mejor?
  • ¿Cuáles son las desventajas con la segunda solución? Si Google descubre de alguna manera que no estoy publicando exactamente el mismo contenido para el bot de Google como usuario habitual, ¿me castigarán en los resultados de búsqueda?
128
user544941

Si bien el # 2 podría ser "más fácil" para usted como desarrollador, solo proporciona rastreo de motores de búsqueda. Y sí, si Google descubre que está sirviendo contenido diferente, es posible que se lo penalice (no soy un experto en eso, pero he oído que eso está sucediendo).

Tanto el SEO como la accesibilidad (no solo para personas discapacitadas, sino también para dispositivos móviles, dispositivos de pantalla táctil y otras plataformas habilitadas para Internet/computación no estándar) tienen una filosofía subyacente similar: un marcado semánticamente rico que es "accesible" (es decir, puede ser accedidos, vistos, leídos, procesados ​​o utilizados de otra manera) para todos estos navegadores diferentes. Un lector de pantalla, un rastreador de motores de búsqueda o un usuario con JavaScript habilitado, deberían poder usar/indexar/comprender la funcionalidad principal de su sitio sin problemas.

pushState no agrega a esta carga, en mi experiencia. Solo trae lo que solía ser una idea de último momento y "si tenemos tiempo" a la vanguardia del desarrollo web.

Lo que describe en la opción # 1 suele ser la mejor manera de hacerlo, pero, al igual que con otros problemas de accesibilidad y SEO, hacer esto con pushState en una aplicación con gran cantidad de JavaScript requiere una planificación inicial o se convertirá en una carga importante. Debería incluirse en la arquitectura de la página y la aplicación desde el principio: la actualización es dolorosa y causará más duplicación de la necesaria.

He estado trabajando con pushState y SEO recientemente para un par de aplicaciones diferentes, y encontré lo que creo que es un buen enfoque. Básicamente sigue su artículo # 1, pero las cuentas no duplican html/templates.

La mayor parte de la información se puede encontrar en estas dos publicaciones de blog:

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

y

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

Lo esencial es que utilizo plantillas ERB o HAML (que ejecutan Ruby on Rails, Sinatra, etc.) para el procesamiento del lado del servidor y para crear las plantillas del lado del cliente que puede usar Backbone, así como para mis especificaciones de JavaScript Jasmine. Esto elimina la duplicación de marcas entre el lado del servidor y el lado del cliente.

A partir de ahí, debe realizar algunos pasos adicionales para que su JavaScript funcione con el HTML que representa el servidor: verdadera mejora progresiva; tomar el marcado semántico que se entregó y mejorarlo con JavaScript.

Por ejemplo, estoy creando una aplicación de galería de imágenes con pushState. Si solicita /images/1 desde el servidor, mostrará toda la galería de imágenes en el servidor y enviará todo el HTML, CSS y JavaScript a su navegador. Si tienes JavaScript deshabilitado, funcionará perfectamente bien. Cada acción que realice solicitará una URL diferente del servidor y el servidor representará todo el marcado para su navegador. Sin embargo, si tiene habilitado JavaScript, el JavaScript recogerá el HTML ya renderizado junto con algunas variables generadas por el servidor y se hará cargo desde allí.

Aquí hay un ejemplo:

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

Después de que el servidor presente esto, el JavaScript lo recogerá (usando una vista Backbone.js en este ejemplo)

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")
  });
});

Este es un ejemplo muy simple, pero creo que se entiende claramente.

Cuando instalo la vista después de que se carga la página, proporciono el contenido existente del formulario que fue renderizado por el servidor, a la instancia de la vista como la el para la vista. Estoy no llamando a render o haciendo que la vista genere una el para mí, cuando se carga la primera vista. Tengo un método de renderizado disponible después de que la vista esté en funcionamiento y la página sea JavaScript. Esto me permite volver a renderizar la vista más tarde si es necesario.

Al hacer clic en el botón "Diga mi nombre" con JavaScript habilitado se generará un cuadro de alerta. Sin JavaScript, se volvería a publicar en el servidor y el servidor podría representar el nombre de un elemento html en algún lugar.

Editar

Considere un ejemplo más complejo, donde tiene una lista que debe adjuntarse (de los comentarios debajo de esto)

Digamos que tienes una lista de usuarios en una etiqueta <ul>. Esta lista fue representada por el servidor cuando el navegador realizó una solicitud, y el resultado se parece a algo como:

<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>

Ahora debe recorrer esta lista y adjuntar una vista y modelo de Backbone a cada uno de los <li> elementos. Con el uso del atributo data-id, puede encontrar el modelo del que proviene cada etiqueta fácilmente. Luego, necesitará una vista de colección y una vista de elementos que sea lo suficientemente inteligente como para adjuntarse a este 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();

En este ejemplo, la UserListView recorrerá todas las etiquetas <li> y adjuntará un objeto de vista con el modelo correcto para cada una. configura un controlador de eventos para el evento de cambio de nombre del modelo y actualiza el texto mostrado del elemento cuando ocurre un cambio.


Este tipo de proceso, para tomar el html que el servidor representó y para que mi JavaScript lo tome y lo ejecute, es una excelente manera de hacer que todo funcione para SEO, Accesibilidad y pushState.

Espero que ayude.

44
Derick Bailey

Creo que necesitas esto: http://code.google.com/web/ajaxcrawling/

También puede instalar un backend especial que "renderiza" su página ejecutando javascript en el servidor, y luego lo sirve para google.

Combina ambas cosas y tendrás una solución sin programarla dos veces. (Siempre y cuando su aplicación sea totalmente controlable a través de fragmentos de anclaje).

22
Ariel

Entonces, parece que la principal preocupación es estar SECO

  • Si está utilizando pushState, haga que su servidor envíe el mismo código exacto para todas las direcciones URL (que no contienen una extensión de archivo para servir imágenes, etc.) "/ mydir/myfile", "/ myotherdir/myotherfile" o root "/ "- todas las solicitudes reciben el mismo código exacto. Necesitas tener algún tipo de motor de reescritura de URL. También puede servir un poquito de html y el resto puede provenir de su CDN (usando require.js para administrar las dependencias - vea https://stackoverflow.com/a/13813102/1595913 ).
  • (pruebe la validez del enlace convirtiendo el enlace a su esquema de url y comprobando la existencia de contenido consultando una fuente estática o dinámica. Si no es válido, envíe una respuesta 404).
  • Cuando la solicitud no proviene de un bot de google, simplemente procesa normalmente.
  • Si la solicitud es de un bot de Google, use phantom.js - navegador webkit sin cabeza ( "Un navegador sin cabeza es simplemente un navegador web completo sin interfaz visual." ) para procesar html y javascript en el servidor y enviar al google bot el html resultante. A medida que el bot analiza el html, puede golpear sus otros enlaces "pushState"/somepage en el servidor <a href="/someotherpage">mylink</a>, el servidor reescribe la url en su archivo de aplicación, lo carga en phantom.js y el html resultante se envía al bot, y así sucesivamente ...
  • Para su html, asumo que está utilizando enlaces normales con algún tipo de secuestro (por ejemplo, con backbone.js https://stackoverflow.com/a/9331734/1595913 )
  • Para evitar confusiones con cualquier enlace, separe su código api que sirve a json en un subdominio separado, por ejemplo. api.mysite.com
  • Para mejorar el rendimiento, puede preprocesar las páginas de su sitio para los motores de búsqueda con anticipación durante las horas de descanso, creando versiones estáticas de las páginas utilizando el mismo mecanismo con phantom.js y, por consiguiente, distribuya las páginas estáticas a los robots de Google. El preprocesamiento se puede realizar con una aplicación sencilla que puede analizar las etiquetas <a>. En este caso, el manejo 404 es más fácil, ya que simplemente puede verificar la existencia del archivo estático con un nombre que contiene la ruta url.
  • Si utiliza #! la sintaxis de hash bang para los enlaces de su sitio aplica un escenario similar, excepto que el motor de servidor de url de reescritura buscará _escaped_fragment_ en la url y le dará formato a la url de su esquema de url.
  • Hay un par de integraciones de node.js con phantom.js en github y puedes usar node.js como el servidor web para producir resultados html.

Aquí hay un par de ejemplos que usan phantom.js para seo:

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

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

17
Leonidaz

Si estás usando Rails, prueba poirot . Es una joya que hace que resulte sencillo reutilizar bigote o manillares plantillas del cliente y del lado del servidor.

Cree un archivo en sus vistas como _some_thingy.html.mustache.

Lado del servidor de render:

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

Ponga la plantilla de su cabeza para uso del lado del cliente:

<%= template_include_tag 'some_thingy' %>

Lado del cliente de Rendre:

html = poirot.someThingy(my_model)
4
Tim Scott

Para tomar un ángulo ligeramente diferente, su segunda solución sería la correcta en términos de accesibilidad ... estaría proporcionando contenido alternativo a los usuarios que no pueden usar javascript (aquellos con lectores de pantalla, etc.).

Esto agregaría automáticamente los beneficios del SEO y, en mi opinión, Google no lo vería como una técnica "traviesa".

3
Clive

Interesante. He estado buscando soluciones viables pero parece ser bastante problemático.

En realidad me estaba inclinando más hacia tu segundo enfoque:

Permita que el servidor proporcione un sitio web especial solo para los robots de los motores de búsqueda. Si un usuario normal visita http://example.com/my_path el servidor debería darle una versión pesada de JavaScript del sitio web. Pero si el bot de Google visita, el servidor debería darle un mínimo de HTML con el contenido que quiero que indexe Google.

Aquí está mi opinión sobre la solución del problema. Aunque no se ha confirmado que funcione, puede proporcionar alguna idea o idea para otros desarrolladores.

Suponga que está utilizando un marco JS que admite la funcionalidad de "estado Push", y su marco de back-end es Ruby on Rails. Tiene un sitio de blog simple y le gustaría que los motores de búsqueda indexaran todos sus artículos index y show páginas.

Digamos que tienes tus rutas configuradas de esta manera:

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

Asegúrese de que cada controlador del lado del servidor represente la misma plantilla que el marco del lado del cliente requiere para ejecutarse (html/css/javascript/etc). Si ninguno de los controladores coincide en la solicitud (en este ejemplo, solo tenemos un conjunto completo de acciones REST para la ArticlesController), entonces haga coincidir cualquier otra cosa y solo renderice la plantilla y deje que el marco del lado del cliente maneje el enrutamiento. La única diferencia entre golpear un controlador y golpear el comodín de comodín sería la capacidad de representar contenido en función de la URL que se solicitó a los dispositivos deshabilitados para JavaScript.

Por lo que entiendo, es una mala idea representar contenido que no está visible para los navegadores. Entonces, cuando Google lo indexa, la gente visita Google para visitar una página determinada y no hay contenido, por lo que probablemente será penalizado. Lo que me viene a la mente es que representa el contenido en un nodo div que display: none en CSS.

Sin embargo, estoy bastante seguro de que no importa si simplemente haces esto:

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

Y luego usar JavaScript, que no se ejecuta cuando un dispositivo con JavaScript desactivado abre la página:

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

De esta manera, para Google, y para cualquier persona con dispositivos deshabilitados para JavaScript, verían el contenido sin formato/estático. Por lo tanto, el contenido está físicamente allí y es visible para cualquier persona con dispositivos con JavaScript desactivado.

Pero, cuando un usuario visita la misma página y en realidad tiene JavaScript habilitado, el nodo #no-js se eliminará para que no sobrecargue su aplicación. Luego, el marco del lado del cliente manejará la solicitud a través de su enrutador y mostrará lo que un usuario debe ver cuando JavaScript está habilitado.

Creo que esta podría ser una técnica válida y bastante fácil de usar. Aunque eso podría depender de la complejidad de su sitio web/aplicación.

Aunque, por favor corrígeme si no lo es. Solo pensé en compartir mis pensamientos.

1

Use NodeJS en el lado del servidor, identifique su código del lado del cliente y enrute el uri de cada solicitud http (excepto para los recursos http estáticos) a través de un cliente del lado del servidor para proporcionar el primer 'bootnap' (una instantánea de la página en el estado). Use algo como jsdom para manejar jquery dom-ops en el servidor. Después de que regresó el bootnap, configura la conexión websocket. Probablemente es mejor diferenciar entre un cliente websocket y un cliente de servidor haciendo algún tipo de conexión de envoltura en el cliente (el cliente de servidor puede comunicarse directamente con el servidor). He estado trabajando en algo como esto: https://github.com/jvanveen/rnet/

1
Phrearch

Utilice Plantilla de cierre de Google para representar páginas. Se compila a javascript o Java, por lo que es fácil renderizar la página en el lado del cliente o del servidor. En el primer encuentro con cada cliente, renderice el html y agregue javascript como enlace en el encabezado. El rastreador leerá el html solo pero el navegador ejecutará su script. Todas las solicitudes subsiguientes desde el navegador podrían hacerse contra la API para minimizar el tráfico.

0
Aleš Kotnik