Recientemente escribí un comentario en el blog de Alfredo Artiles1, Bitácora de Webmaster, en un artículo relacionado con la nueva propuesta de los señores de google para hacer indexable un site en Ajax. En su artículo Alfredo resumía que aunque vayan por el buen camino al intentarlo, el uso de las técnicas que proponía Google para conseguirlo no le parecía muy adecuado. Yo voy un paso más allá y diría que sería todo un error implementar las opciones que comenta Google, pero esto daría para otro artículo2.
La cuestión es que su post viene a cuento porque hace tiempo que os quería comentar como, creo, que debería hacerse una web con contenido en AJAX y creo que este sería el mejor momento para ello.
Lo primero, comentaros que el problema de AJAX no solo está con los buscadores, sino principalmente con la usabilidad y accesibilidad. Ese pequeño porcentaje de usuarios que llega al site desde navegadores especiales para discapacitados, o desde navegadores solo texto, o incluso en un porcentaje mayor, aquellos que acceden desde su móvil, ellos son el principal objetivo a solventar cuando se desarrolla en AJAX, no los buscadores que en este caso creo que son secundarios (aunque también salgan muy beneficiados).
Bueno, entonces la panacea al respecto, cual sería? Simple, no desarrollar en AJAX!
Esto que parece una perogrullada es realmente la mejor solución para corregir el problema, desarrollar sin AJAX y añadir la capa de AJAX a posteriori. Con esto conseguimos crear una navegación lo más sólida posible fuera de todo JavaScript, y una vez creada añadimos mediante JS la capa AJAX, a base de modificar los enlaces internos por llamadas asíncronas al servidor y un par más de detalles en el código. Así, si un navegador sin JS o un Bot llegan a tu site con ganas de recorrerlo entero no tendrán ningún problema y podrán navegar por todo el site de forma perfecta.
También están los beneficios añadidos, como poder abrir enlaces en pestañas/ventanas nuevas, crear otra capa de independencia entre la Vista y los Scripts (digamos MVSC), o uno más importante aun el de poder enlazar directamente una página interna desde cualquier lado sabiendo que va a llegar a dicho enlace.
Nota: A partir de aquí el asunto se pone muy denso, así que si lo tuyo no es la programación front o bakend o la maquetación o si no comprendes algo de AJAX, mejor quédate con la idea vista hasta ahora y mira ejemplos en, por ejemplo, la web de Coleccionismo Baral
Lo primero que haremos será dar por sentado unos conceptos de la programación del site:
- Se programa OOP (aunque en este caso no sea necesario, pero así vamos inculcando las nociones básicas mínimas)
- Usamos el patrón de diseño de software MVC, separando la Vista y el Control sobretodo.
- Usamos también el patrón Façade para tener así un único punto de entrada al código.
- Usamos URLs SEO friendly del tipo http://host/casa/coche que luego pasarán a ser http://host#casa/coche
Bueno, una vez está claro esto, empezamos con la programación del site, creamos la navegación básica, la lógica interna del site, los modelos,… En fin todo el desarrollo pero sin incluir nada de AJAX por ahora. Una vez realizada la navegación y teniendo en cuenta que usamos los patrónes Façade y MVC incluimos un pequeño script3 al código del site similar al siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | //Script File // variable general que nos servirá para chequear si hay que cambiar contenidos por AJAX var navAjaxCheck = false; // Función goto, encargada de hacer la llamada asíncrona // La variable jsonValue incorpora toda la información necesaria var goto = function (jsonValue) { // Posibles excepciones a la navegación por AJAX if (location.hash.test("signup")) return false; //waiter es para añadir el típico efecto de sombreado en el site mientras se cambia el contenido. jsonValue.waiter = jsonValue.waiter === false ? false : true; //update es el id del contenedor a remplazar // (false en caso de no quere actualizar nada y "contenedor general" en caso de "null") jsonValue.update = jsonValue.update ? jsonValue.update : (jsonValue.update === false ? false : 'contenedor_general'); // El nuevo valor del href jsonValue.href = jsonValue.href.test("#") ? " " : jsonValue.href; new Request.HTML({ // Aquí está el punto importante, mandamos toda la info a /ajax/ url: '/ajax/'+JSON.encode(jsonValue), /* Se desactiva por fallos con el IE. update: $(jsonValue.update), useWaiter: jsonValue.waiter, waiterOptions: { baseHref: '/', img: { src: 'img/loader.gif', styles: { width: '220', height: '20' } }, layer: { background: '#fff', opacity: 0.9 } }, waiterTarget: jsonValue.update, */ // Una vez completado guardamos el href y la información donde corresponde onComplete: function(responseTree, responseElements, responseHTML, responseJavaScript){ $(jsonValue.update).set('html',responseHTML); //reseteamos el init() ya que hay contenido nuevo en el site. init(); if (jsonValue.href && jsonValue.replace) location.hash = "#"+ jsonValue.href.replace(jsonValue.replace,""); //reseteamos la variable navAjaxCheck para contener la información del site actual navAjaxCheck = location.hash; } }).send(); }; //Función recursiva encargada de hacer de watchdog para la navegación asíncrona (dotando de la opción de retroceder/avanzar) var navAjax = function() { // Posibles excepciones a la navegación por AJAX if (location.href.test("signup")) return false; // Esto se da en el caso de que estemos en una URL normal tipo /casa/coche, que rápidamente la formateamos a // #casa/coche para que siga la lógica de navegación asíncrona. if (location.pathname != '/') { var href = location.href; var value = 'http://'+WEBHOST+'/'; // WEBHOST es una variable definida anteriormente location.href = href.replace(value,value+"#"); // por compatibilidad con IE // Comprobamos con el check generado si ha cambiado el hash de la URL, // en cuyo caso llamamos a goto con el nuevo hash }else if (location.hash != navAjaxCheck) { var href = location.hash; var value = '#'; goto({ href: href.replace(value,''), waiter:false, replace:"" }); } }; //Función principal encargada de cambiar todos los enlaces internos por llamadas asíncronas. var addAjax = function () { // Usamos una frase de testeo simple, el dominio del site (también podría ser algo más complicado mediante exp. regulares) var replace = 'http://'+WEBHOST+'/'; // Buscamos todos los enlaces del site $$('a').each(function(el) { var href = el.href; // Buscamos los que enlazan dentro del site y evitamos los marcados como "noAjax" // a los encontrados así les quitamos la opción por defecto y añadimos la opción de Ajax if (href.test(replace) && el.className != "noAjax") el.addEvent('click', function(event) { event.preventDefault(); goto({href: href, replace: replace}); }); }); // función con las llamadas necesarias al cargar el site (incluir el navAjax, chequear variables...) var init = function() { // Añadimos el ajax addAjax(); // .... } // Añadimos los eventos justo cuando el DOM ya se haya cargado. window.addEvent('domready', function() { // Lanzamos el control de AJAX con un periodical para que se ejecute continuamente // el tiempo aproximado depende de la carga de JS del site y al gusto de cada uno (entre 1 y 5 segundos) navAjax.periodical(1000); // Cargamos el init con la información necesaria. init(); // Cargamos el resto de funcionalidades que solo son necesarias una vez. } }; |
Como podréis ver, este JS está bastante mal codificado, lo suyo sería crear una clase completa que se encargue de toda la parte de navegación AJAX y hacerle mucho refactoring para conseguir algo decente, pero la idea es explicar los principios de acción, no daros un código 100% válido y correcto.
Ahora, solo tendremos que añadir al controller de la aplicación un método para cuando llegue por “/ajax/” que:
- Defina la constante
AJAX = TRUE4. - Coja la información pasada por la variable json del segundo parámetro y cambie la REQUEST_URI por la url del mismo.
1 2 3 4 5
//Algo similar a esto debería valer $json = /* variable con la información */ $ajax = json_decode(stripslashes($json), TRUE); $url = $ajax["href"] ? $ajax["href"] : $_SERVER["HTTP_REFERER"]; $_SERVER['REQUEST_URI'] = str_replace("http://".$_SERVER['SERVER_NAME'], '', $url);
- Continue de forma normal con el controller
Ahora la navegación para el resto del código es normal, hasta que llegamos a la parte de vista, donde utilizando la contante definida AJAX para saber si el usuario está utilizando AJAX o no y en caso afirmativo mostrar solo la información referente al AJAX que queremos, evitando mostrar por ejemplo la sección de “header” o las secciones fijas del template como el footer… Eso a gusto.
Como veis no es tan difícil añadir después la capa de AJAX y así se consigue una navegación con y sin JS casi-perfecta que facilitará y mucho la vida a navegadores y buscadores.
TODO: como observaréis el código JS está muy sucio y habría que hacer un refactoring completo de él. También habría que añadirle la posibilidad de controlar las anclas que ya existieran en el código (por ejemplo quitándolas y añadiendo un efecto de movimiento en la página al clicar sobre el enlace).
La parte del Controller y la de Vista las he dejado muy abiertas, esto es porque aquí cada maestrillo tiene su librillo, así que no he querido meterme mucho en como lo hago yo para no complicar más las cosas.
Y por último decir que en mi caso empecé usando una constante para definir el AJAX, pero rápidamente deseché esta opción ya que es posible que sobre una página se realicen llamadas sobre distintas secciones por ejemplo y al definir solo una constante esto no se cumple. Aquí lo que suelo hacer5 es pasar la información a través de la variable json(“update”) los campos a modificar, e incluir un código similar a este en el método que controla el AJAX para pasarlo a la parte de vista.
if ($ajax["update"]) { $zonas = explode ("::",$ajax["update"]); foreach ($zonas as $key => $value) $ajax[$value] = true; }
- aka @aartiles ↩
- Solo diré que con lo fácil de modificar que dicen que es, por qué los señores de Google no han realizado ya los cambios en su código? ↩
- Ojo, que estamos trabajando con Mootools y ClientCide y con un código bastante sucio y obsoleto, por cierto ↩
- Esto es ligeramente más complicado, pero para simplificar dejemoslo así por ahora ↩
- Aunque también habría que refactorizar ya que no me llega a convencer ↩

Muy buena reflexión/propuesta
. La solución a todos nuestros problemas estaría en que el protocolo HTTP transmitiera la parte de la URL después del #, así podríamos parsear el $_SERVER['REQUEST_URI'] y obtener los parámetros AJAX de forma natural,… pero en fin… tenemos que adaptarnos y ser creativos.
Para la función navAjax te recomiendo que si no lo has hecho aun mires el History Manager para Mootools: http://digitarald.de/project/history-manager/. Funciona muy bien en todos los navegadores incluido IE6. Es lo que suelo utilizar en todos mis proyectos http://locafollow.com
Una cosa que no entiendo es para qué modificas el $_SERVER['REQUEST_URI'], tiene eso alguna consecuencia que yo no conozca?
Os recomiendo que estudiéis http://go2web20.net, manejan muy bien el tema del AJAX sin limitar el indexado de los buscadores y su premio es el posicionamiento que están alcanzando.
El hacer el cambio $_SERVER['REQUEST_URI'] es simplemente porque así al controller le pasas la información como si fuese /casa/coche en vez de #casa/coche y así puedes seguir de forma natural.
En cuanto a HistoryManager lo he usado más de una y más de dos veces, pero haciéndolo como te indico no se hace necesario incluirlo.
Y por último, go2web20, ya lo leí en tu artículo, pero veo unas cositas que no me gustaron… primero eso de tener JS intrusivo… ains la semántica web y luego que solo vi Ajax en el paginado de la portada y me juego un duro a que las segundas hojas del paginado no las lee
Hola Angel,
Sin animo de molestar (ya que lo que pongo no está a la altura, ni desarrollado hasta el punto, de tu artículo) dejo aquí una solución que publique hace un tiempo en mi blog bajo la misma filosofía que comentas pero que simplemente muestra los principios de como conseguir eso mismo con jquery.
http://blog.ikhuerta.com/posicionar-ajax-seo-con-aja
@ikhuerta molestia?? Todo lo contrario!! muchas gracias por la aportación!! la miraré con calma (eso si, ya mañana) y a ver si entre todos sacamos una idea 100% válida que nos guste a todos!!
Bienvenido y muchas gracias por la aportación