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.
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.
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 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:
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:
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:
Para obtener estos objetivos, en nuestra aplicación de angular vamos a implementar la siguiente solución:
Bueno. Conocemos la teoria. Vamos a practicar ahora.
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:
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.
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>
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.
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