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:
http://example.com/my_path
directamente, el servidor representará lo mismo que el cliente si voy a /my_path
a través de pushState.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:
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:
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.
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).
Entonces, parece que la principal preocupación es estar SECO
<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 ...<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.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
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)
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".
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.
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/
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.