javascript - angularjs seo
Sitios web JS "de una sola página" y SEO (8)
En la actualidad, existen muchas herramientas geniales para crear poderosos sitios web de JavaScript de "una sola página". En mi opinión, esto se hace de forma correcta, permitiendo 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 de motor de búsqueda. Puedo pensar en dos soluciones:
- Cuando el usuario ingresa al sitio web, permita que el servidor represente la página exactamente como lo haría el cliente en la navegación. Así que si voy directamente a
http://example.com/my_path
el servidor mostraría lo mismo que el cliente si voy a/my_path
través de pushState. - Deje 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 de JavaScript del sitio web. Pero si el bot de Google visita, el servidor debería darle un HTML mínimo con el contenido que quiero que Google indexe.
La primera solución se analiza más here . He estado trabajando en un sitio web haciendo esto y no es una experiencia muy agradable. No es SECO 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 Flash. Me gusta este enfoque mucho más que el primero y con la herramienta adecuada en el servidor podría hacerse sin ningún problema.
Entonces, lo que realmente me pregunto es lo siguiente:
- ¿Puedes pensar en alguna solución mejor?
- ¿Cuáles son las desventajas con la segunda solución? Si Google de alguna manera descubre que no estoy publicando exactamente el mismo contenido para el robot de Google como un usuario normal, ¿entonces sería castigado en los resultados de búsqueda?
Creo que necesitas esto: http://code.google.com/web/ajaxcrawling/
También puede instalar un backend especial que "renderice" su página ejecutando javascript en el servidor y luego sirva para google.
Combina ambas cosas y tendrás una solución sin programar las cosas dos veces. (Siempre que su aplicación sea totalmente controlable a través de fragmentos de anclaje).
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 URL (que no contengan 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 (utilizando require.js para administrar dependencias, consulte https://.com/a/13813102/1595913 ).
- (Pruebe la validez del enlace convirtiendo el enlace a su esquema de URL y probando 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 robot de Google, simplemente procesa normalmente.
- Si la solicitud es de un bot de Google, use phantom.js - browser webkit sin cabeza ( "Un navegador sin cabeza es simplemente un navegador web con todas las funciones sin interfaz visual" ) para renderizar html y javascript en el servidor y enviar el google bot el html resultante. A medida que el bot analiza el html puede golpear sus otros enlaces "pushState" / alguna página en el servidor
<a href="/someotherpage">mylink</a>
, el servidor reescribe la url en su archivo de aplicación, la carga en phantom.js y el html resultante se envía al robot, y así sucesivamente ... - Para su html estoy asumiendo que está utilizando enlaces normales con algún tipo de secuestro (por ejemplo, usando backbone.js https://.com/a/9331734/1595913 )
- Para evitar confusiones con cualquier enlace, separe su código api que sirve a json en un subdominio separado, p. Ej. 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 libres mediante la creación de versiones estáticas de las páginas utilizando el mismo mecanismo que phantom.js y, en consecuencia, servir las páginas estáticas a Google Bots. El preprocesamiento se puede hacer con una aplicación simple que puede analizar las etiquetas
<a>
. En este caso, el manejo de 404 es más fácil ya que puede simplemente 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 se aplica a un escenario similar, excepto que el motor de reescritura del servidor de URL buscaría _escaped_fragment_ en la url y formatearía la url en su esquema de url.
- Hay un par de integraciones de node.js con phantom.js en github y puede usar node.js como servidor web para producir resultados html.
Aquí hay un par de ejemplos usando phantom.js para seo:
http://backbonetutorials.com/seo-for-single-page-apps/
http://thedigitalself.com/blog/seo-and-javascript-with-phantomjs-server-side-rendering
Interesante. He estado buscando soluciones viables, pero parece ser bastante problemático.
En realidad, me estaba inclinando más hacia tu segundo enfoque:
Deje 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 de JavaScript del sitio web. Pero si el bot de Google visita, el servidor debería darle un HTML mínimo con el contenido que quiero que Google indexe.
Aquí está mi opinión sobre cómo resolver el problema. Aunque no está confirmado que funcione, podría proporcionar una idea o idea para otros desarrolladores.
Supongamos que está utilizando un marco JS que admite la funcionalidad de "estado de inserción", y su marco de fondo es Ruby on Rails. Usted tiene un sitio de blog simple y le gustaría que los motores de búsqueda indexen todo el índice de su artículo y las páginas de las mismas.
Digamos que tienes tus rutas configuradas así:
resources :articles
match "*path", "main#index"
Asegúrese de que todos los controladores del lado del servidor presenten la misma plantilla que requiere el marco del lado del cliente para ejecutar (html / css / javascript / etc). Si ninguno de los controladores coincide en la solicitud (en este ejemplo, solo tenemos un conjunto de acciones RESTful para ArticlesController
), simplemente haga coincidir cualquier otra cosa y simplemente 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 comodín sería la capacidad de presentar contenido basado en la URL que se solicitó a los dispositivos desactivados de JavaScript.
Por lo que entiendo, es una mala idea presentar contenido que no sea visible para los navegadores. Entonces, cuando Google lo indexa, las personas pasan por Google para visitar una página determinada y no hay ningún contenido, entonces probablemente lo penalicen. Lo que me viene a la mente es que representas 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 usa JavaScript, que no se ejecuta cuando un dispositivo con JavaScript habilitado abre la página:
$("#no-js").remove() # jQuery
De esta forma, para Google y para cualquier persona con dispositivos desactivados por JavaScript, verían el contenido sin procesar / estático. Entonces, el contenido está físicamente allí y es visible para cualquier persona que tenga dispositivos con JavaScript desactivado.
Sin embargo, cuando un usuario visita la misma página y tiene habilitado JavaScript, se eliminará el nodo #no-js
para que no obstruya su aplicación. Luego, su marco de trabajo del lado del cliente manejará la solicitud a través de su enrutador y mostrará lo que un usuario debería 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.
Sin embargo, por favor corrígeme si no es así. Solo pensé en compartir mis pensamientos.
Mientras que el # 2 podría ser "más fácil" para usted como desarrollador, solo proporciona el rastreo de motores de búsqueda. Y sí, si Google descubre que usted sirve contenido diferente, podría ser penalizado (no soy un experto en eso, pero he oído que esto sucede).
Tanto el SEO como la accesibilidad (no solo para personas discapacitadas, sino también para accesibilidad a través de dispositivos móviles, dispositivos de pantalla táctil y otras plataformas informáticas / Internet no estándar) tienen una filosofía subyacente similar: el marcado semánticamente rico que es "accesible" (es decir, ser accedido, visto, leído, procesado o utilizado de otra forma) a todos estos navegadores diferentes. Un lector de pantalla, un rastreador de motor de búsqueda o un usuario con JavaScript habilitado, todos deberían poder usar / indexar / comprender la funcionalidad central de su sitio sin problema.
pushState
no pushState
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 describa en la opción n. ° 1 suele ser la mejor opción, pero, al igual que otros problemas de accesibilidad y SEO, hacer esto con pushState
en una aplicación con JavaScript requiere una planificación pushState
o se convertirá en una carga significativa. Debería estar integrado en la página y en la arquitectura de la aplicación desde el principio: la actualización es dolorosa y causará más duplicaciones de las necesarias.
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ía de la información se puede encontrar en estas dos publicaciones de blog:
y
http://lostechies.com/derickbailey/2011/06/22/rendering-a-rails-partial-as-a-jquery-template/
La esencia de esto es que utilizo plantillas ERB o HAML (ejecutando Ruby on Rails, Sinatra, etc.) para mi renderización del lado del servidor y para crear las plantillas del lado del cliente que Backbone puede usar, así como para mis especificaciones de Jasmine JavaScript. Esto corta la duplicación de marcado 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 construyendo una aplicación de galería de imágenes con pushState
. Si solicita /images/1
desde el servidor, renderizará toda la galería de imágenes en el servidor y enviará todo el HTML, CSS y JavaScript a su navegador. Si tiene JavaScript desactivado, funcionará perfectamente bien. Cada acción que realice requerirá una URL diferente del servidor y el servidor representará todas las marcas para su navegador. Sin embargo, si tiene JavaScript habilitado, el JavaScript recogerá el HTML ya renderizado junto con algunas variables generadas por el servidor y asumirá el control 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 lo presente, el JavaScript lo levantará (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 entiende bien.
Cuando realizo la vista inmediatamente después de que se carga la página, proporciono el contenido existente del formulario que fue procesado por el servidor, a la instancia de vista como el
de la vista. No llamo a render ni a que la vista genere un el
para mí cuando se carga la primera vista. Tengo un método de renderizado disponible para después de que la vista esté funcionando y la página sea todo JavaScript. Esto me permite volver a renderizar la vista más tarde si es necesario.
Al hacer clic en el botón "Decir mi nombre" con JavaScript habilitado se generará un cuadro de alerta. Sin JavaScript, se publicaría de nuevo en el servidor y el servidor podría representar el nombre de un elemento html en alguna parte.
Editar
Considere un ejemplo más complejo, donde tiene una lista que debe adjuntarse (a partir de los comentarios a continuación)
Supongamos que tiene una lista de usuarios en una etiqueta <ul>
. Esta lista fue procesada por el servidor cuando el navegador realizó una solicitud y el resultado es similar a:
<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 necesita recorrer esta lista y adjuntar una vista y modelo de Backbone a cada uno de los <li>
elementos. Con el uso del atributo de data-id
, puede encontrar el modelo del que proviene cada etiqueta fácilmente. Necesitará una vista de colección y una vista de elemento 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, 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 prestó y hacer que mi JavaScript se haga cargo de él y ejecutarlo, es una excelente manera de poner en marcha las cosas para SEO, accesibilidad y soporte de pushState
.
Espero que ayude.
Para tomar un ángulo ligeramente diferente, su segunda solución sería la correcta en términos de accesibilidad ... proporcionaría 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 consideraría una técnica "traviesa".
Si está utilizando Rails, intente con poirot . Es una joya que hace que sea muy sencillo reutilizar las plantillas de mustache o handlebars lado del cliente y del servidor.
Cree un archivo en sus vistas como _some_thingy.html.mustache
.
Renderizar lado del servidor:
<%= render :partial => ''some_thingy'', object: my_model %>
Coloque la plantilla en su cabeza para uso del lado del cliente:
<%= template_include_tag ''some_thingy'' %>
Rendre lado del cliente:
html = poirot.someThingy(my_model)
Use Google Closure Template para representar páginas. Se compila a javascript o java, por lo que es fácil representar 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. Crawler solo leerá el html pero el navegador ejecutará su script. Todas las solicitudes posteriores del navegador podrían realizarse en contra de la API para minimizar el tráfico.
Use NodeJS en el servidor, exploreify su código de cliente y enrute uri de cada solicitud HTTP (a excepción de recursos http estáticos) a través de un cliente en el servidor para proporcionar el primer ''arranque'' (una instantánea de la página de su estado). Use algo como jsdom para manejar jquery dom-ops en el servidor. Después de que se devuelva el bootsnap, configure la conexión del websocket. Probablemente sea mejor diferenciar entre un cliente de websocket y un cliente de servidor haciendo una especie de conexión de contenedor 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/