SEO Y ANGULAR

Este proyecto consisiterá en crear un Server Side Rendering (SSR) y cómo implementarlo para que nuestra aplicación SPA con Angular pueda ser detectada por la web (SEO) haciendo uso de Angular Universal (@nguniversal/express-engine)

Si necesitas ayuda esta disponible el siguiente video explicativo: https://www.youtube.com/watch?v=J8PDfVY2FfI.

Peligro de SPA con el SEO

La gran ventaja de las aplicaciones SPA(Aplicaciones del lado cliente) es que trabajan en el lado del cliente. Por ejemplo una web creada en el framework de Angular se ejecuta en el cliente, es decir, en el navegador del usuario y no en un servidor remoto. Este tipo aplicaciones web muestran todas las pantallas en una misma página, sin recargar el navegador.

El problema surge con Google. Cuando se rastrea una web; los robots/rastreadores de motores de búsqueda de Google o Yahoo funcionan de forma similar al comando curl. ¿Por qué? Simple, iniciar el navegador y ejecutar JavaScript es una tarea costosa y, por lo tanto, no viable. No hay tiempo ni recursos para hacerlo cuando necesita buscar miles de páginas.

El ejemplo real

Creemos una aplicación angular y verifiquemos cómo la ven los robots:

  ng new myApp
  cd myApp
  ng serve

Ahora, en la otra ventana de terminal, ejecute el comando curl y echamos un vistazo a la salida:

$ curl localhost:4200

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>MyApp</title>
        <base href="/" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" type="image/x-icon" href="../../../../favicon.ico" />
    </head>
    <body>
        <app-root></app-root>
        <script type="text/javascript" src="runtime.js"></script>
        <script type="text/javascript" src="polyfills.js"></script>
        <script type="text/javascript" src="styles.js"></script>
        <script type="text/javascript" src="vendor.js"></script>
        <script type="text/javascript" src="main.js"></script>
    </body>
</html>

Como podemos observar, no renderiza correctamente la web y nuestra aplicación web con Angular no ha sido detectada correctamente, es decir, los motores de búsqueda consideran que no hay contenido.

La solución

La solución a este problema es usar @nguniversal/express-engine . Con este módulo podremos utilizar el Server Side Rendering para renderizar el contenido en el servidor y así servirla al usuario completa.

¿Que necesitamos? Para ello necesitamos tener instalado:

  • Tener instalado Node y NPM.
  • Tener instalado Angular CLI

¿Qué nos ofrece Angular Universal?

Es una tecnología que nos permitirá ejecutar nuestra aplicación Angular desde el servidor.

Genera páginas de aplicaciones estáticas en el servidor mediante el proceso denominado SSR (server side rendering o renderizado de lado del servidor) que nos permitirá cubrir las áreas oscuras que se dan en el desarrollo moderno:

  • Facilitar la indexacion del SEO
  • Mejorar el rendimiento de la aplicacion en dispositivos de baja potencia
  • Mostrar la primera pagina rápidamente

La importancia de implementar SEO en un proyecto

Es una parte importantísima para cualquier negocio sea pequeño, mediano o grande. El objetivo del SEo es conseguir que visiten nuestra página web o la de nuestra empresa la mayor cantidad de gente posible y para ello se definen una serie de procedimiento y técnias que permitirán tener una mayor repercusión a nuestra web

Hay dos tipos:

  • SEO on page: se trata de todo lo que puedes hacer respecto al código de nuestra web para que genere más tráfico y es lo que vamos a ver en esta sección
  • TSEO off page: como su nombre indica es todo lo relacionado con tu negocio fuera de tu página, es decir, lo que corresponde a redes sociales, publicidad, etc.

Para obtener estos objetivos, en nuestra aplicación de angular vamos a implementar la siguiente solución:

seo angular

En ella se observa:
  • El usuario solicita la página.
  • La solicitud llega al servidor
  • NodeJS genera HTML y lo envía al navegador
  • El navegador muestra la vista desde el HTML e inmediatamente la muestra al usuario, simultáneamente se está ejecutando JavaScript
  • Cuando JavaScript finaliza el arranque de la aplicación, cambia la vista representada en HTML con la aplicación angular
  • El usuario ve una aplicación angular totalmente interactiva

Bueno. Conocemos la teoria. Vamos a practicar ahora.

Aplicando SSR con Angular Universal

Ahí es donde @nguniversal/express-engine entra en el juego. Escriba un comando simple:


  ng add @nguniversal/express-engine

Hay que recordar que el CLI de Angular utilizar la directiva “ng add” del principio del uso de los schematicas para modificar nuestro código y adaptarlo a la nueva funcionalidad que queremos implementar.

Si hicieramos manualmente el proceso tendríamos que seguir los siguientes pasos, que como podéis ver son muchos:

  • Instalar nuevas dependencias
  • Editar el fichero main.ts
  • Editar el fichero app.module.ts
  • Editar el fichero angular.json
  • Crear el fichero src/app/app.server.module.ts
  • Crear el fichero src/main.server.ts
  • Crear el fichero server.ts
  • Crear el fichero tsconfig.server.json
  • Crear el fichero webpack.server.config.js
  • Editar el fichero angular.json
  • Editar el fichero package.json

Ahora que ya tenemos nuestra aplicación adaptada al renderizado por el lado del servidor (SSR), con lo visto hasta ahora, nuestra aplicación estará más que cubierta en cuanto a ofrecer la mejor experiencia para usuarios al tiempo que conseguiremos enviar contenido indexable para robots… mejorando este punto negativo en las aplicaciones de tipo SPA.

Lanzar la aplicación

Para lanzar la aplicación se ejecutan los siguientes comandos:


  npm run build:ssr
  npm run serve:ssr

Y si ejecutamos la misma sentencia que antes nos encontramos con el suguiente código html:

  curl localhost:4000

      
 <!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <title>MyApp</title>
        <base href="/" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" type="image/x-icon" href="../../../../favicon.ico" />
        <link rel="stylesheet" href="styles.34c57ab7888ec1573f9c.css" />
        <style ng-transition="app-root"></style>
    </head>
    <body>
        <app-root _nghost-c0="" ng-version="6.0.3">
            <div _ngcontent-c0="" style="text-align: center;">
                <h1 _ngcontent-c0="">Welcome to app!</h1>
                <img
                    _ngcontent-c0=""
                    alt="Angular Logo"
                    src="data:image/svg+xml;base64,
  PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTAgMjUwIj4KICAgIDxwYXRoIGZpbGw
  9IiNERDAwMzEiIGQ9Ik0xMjUgMzBMMzEuOSA2My4ybDE0LjIgMTIzLjFMMTI1IDIzMGw3OC45LTQzLjcgMTQuMi0xMjMuMXoiIC8+CiA
  gICA8cGF0aCBmaWxsPSIjQzMwMDJGIiBkPSJNMTI1IDMwdjIyLjItLjFWMjMwbDc4LjktNDMuNyAxNC4yLTEyMy4xTDEyNSAzMHoiIC8+CiAgICA8cGF0
  aCAgZmlsbD0iI0ZGRkZGRiIgZD0iTTEyNSA1Mi4xTDY2LjggMTgyLjZoMjEuN2wxMS43LTI5LjJoNDkuNGwxMS43IDI5LjJIMTgzTDEyNSA1Mi4xem0x
  NyA4My4zaC0zNGwxNy00MC45IDE3IDQwLjl6IiAvPgogIDwvc3ZnPg=="
                    width="300"
                />
            </div>

            <h2 _ngcontent-c0="">Here are some links to help you start:</h2>
            <ul _ngcontent-c0="">
                <li _ngcontent-c0="">
                    <h2 _ngcontent-c0="">
                        <a _ngcontent-c0="" href="https://angular.io/tutorial" rel="noopener" target="_blank">Tour of Heroes</a>
                    </h2>
                </li>
                <li _ngcontent-c0="">
                    <h2 _ngcontent-c0="">
                        <a _ngcontent-c0="" href="https://github.com/angular/angular-cli/wiki" rel="noopener" target="_blank"> CLI Documentation</a>
                    </h2>
                </li>
                <li _ngcontent-c0="">
                    <h2 _ngcontent-c0="">
                        <a _ngcontent-c0="" href="https://blog.angular.io/" rel="noopener" target="_blank">Angular blog</a>
                    </h2>
                </li>
            </ul>
        </app-root>
        <script type="text/javascript" src="runtime.a66f828dca56eeb90e02.js"></script>
        <script type="text/javascript" src="polyfills.2f4a59095805af02bd79.js"></script>
        <script type="text/javascript" src="main.178573f0f1b826344a91.js"></script>
        <script id="app-root-state" type="application/json">
            {}
        </script>
    </body>
</html>

¿Qué pasa con window y localStorage?

NodeJS y los navegadores pueden ejecutarse en 99% de los casos con el mismo código. Pero hay algunas cosas que se pueden ejecutar solo en NodeJS o solo en el navegador. Ahora nos centraremos en resolver estas incidencias

Window: es el navegador que utiliza el cliente. Un rastreador no utiliza una ventana entonces, si por ejemplo queremos utilizar el siguiente código nos daria un error:

  public ngOnInit (): void {
  console.log (window.URL);
  }
    

En mi caso, para ese problema he aplicado la siguiente solución:

  

    import {Component, Inject, OnInit, PLATFORM_ID} from '@angular/core';
import {isPlatformBrowser} from '@angular/common';
@Component ( {
...

export class xxxxComponent implements OnInit {
    isBrowser;

  constructor(@Inject(PLATFORM_ID) private platformId, private deviceService: DeviceDetectorService) {
    this.isBrowser = isPlatformBrowser(platformId);
  }
    ngOnInit() {
    if (this.isBrowser) {

      if (document.cookie.indexOf(this.cookie_name + '=1') >= 0) {
    ....
      }

    }
  }
  

Es decir, con PLATFORM_ID puedo comprobar si hay o no una ventana solicitando la información. En caso afirmativo, genero el código necesario para que se muestre la lógica necesaria, en caso negativo, muestro la lógica deseada en ese caso.

Puesta en producción

En mi caso, he decidido utilizar pm2 para ejecutar el servidor, próximamente creare un proyecto explicando como llevar a cabo una puesta en marcha de un proyecto en angular de estas caracteristicas, pero mientras tanto dejo el siguiente comando:

  sudo npm install -g pm2
  

Una vez instalado, tengo que copiar el directorio raiz (por ejemplo, utilizando el comando scp) creado por el comando:

   npm run build:ssr
  

EL directorio raiz es por defector /dist pero depende de la configuración del proyecto. Una vez copiado el directorio, simplemente se genera un fichero con el siguiente código:

  
  // generated by @nguniversal/express-engine
  const port = process.env.PORT || 8590;

  const server = require('./dist/server');

  server.app.listen(port, () => {
  console.log(`Listening on: http://localhost:${port}`);
  });
  
  

Con este fichero creado y el entorno pm2 instalado se ejecuta el siguiente comando:

  pm2 start node filename.js (en mi caso server.js)
  

Y ya tendremos en ejecutación en la url http://xxxx:8590 nuestro Server Side Rendering. Si por ejemplo estamos en un entorno apacher, si añadimos a nuestro archivo .conf del dominio las siguientes tres linea redireccionariamos las peticiones 80 al Server Side Rendering de la siguiente forma:

  

  <VirtualHost www.example.com:80>
    ProxyPreserveHost On
    ProxyRequests Off
    ServerName www.example.com
    ServerAlias example.com
    ProxyPass / http://localhost:8080/
    ProxyPassReverse / http://localhost:8080/
  </VirtualHost>
  
      

Es necesario tener instalado tanto el paquete proxy y proxy_http de apache y configurado.

    sudo a2enmod proxy
    sudo a2enmod proxy_http