Direkt zum Inhalt

SEO-Freundlich mit Angular CLI 6.0.2 & Universal

SEO-Freundlich mit Angular CLI 6.0.2 & Universal

 

Von Chaiwut Sittiboonta: Wenn soziale Medien und Suchmaschinen die Daten einer Website abgreifen, wird davon ausgegangen, dass sofort das komplette HTML-Markup inklusiver aller Daten verfügbar ist, die Nutzern als Preview angezeigt werden sollen. 

Angular ist ein sogenanntes Single Page Application (SPA) Framework, das innerhalb eines Browsers agiert und das während der Nutzung kein konstantes Neuladen der Website erfordert. Das SPA liefert lediglich das HTML, während Content, Style und State direkt im Client verarbeitet werden, also beispielsweise in Google Chrome

Eigentlich ja sehr praktisch, doch leider bekommen wir in Kombination mit dem ersten Absatz ein Problem: Wenn eine Angular-Appliaktion zu SEO-Zwecken von einer Suchmaschine indexiert wird oder wenn Social Media Plattformen auf Meta-Tags und relevante Seiteninhalte zugreifen wollen: Was sehen die kleinen Software-Helferlein dann? Nur einen kleinen Fitzel unserer HTML und sonst nichts. 

Angular Universal ist eine serverseitige Rendering-Library für Angular Apps, die ebenfalls vom Angular-Team entwickelt wurde. Sie ist:

SEO Freundlich
Eine Suchmaschine kann unsere Webseite nun mit Meta-Tags und relevantem Page Content indexieren.

Social Media Freundlich
Social Media Seiten können auf Websiten-Inhalte zugreifen und Previews liefern. Außerdem kann Universal

Pre-Rendern
Der Cache rendert Websiten von der Server-Seite und stellt sie bereit.

Legen wir gleich los 
Zuerst müssen wir sicherstellen, dass wir Angular CLI in der aktuellsten Version installiert haben. 

$ npm install -g @angular/[email protected]

Dann erstellen wir ein neues Projekt mit Sass Syntax. Mehr Informationen sind unter folgendem Link zu finden: https://stackoverflow.com/a/39816365

$ ng new --style=scss demo-universal

$ cd demo-universal

Dann die Dependencies installieren.

$ npm install --save @angular/platform-server @nguniversal/module-map-ngfactory-loader ts-loader

Damit unsere App kompatibel mit Universal wird, müssen wir src/app/app.module.ts bearbeiten, indem wir die App ID um withServerTransition() erweitern.
 

@NgModule({
 declarations: [AppComponent],
 imports: [
   BrowserModule.withServerTransition({appId: 'demo-universal'}),
 ],
 providers: [],
 bootstrap: [AppComponent]
})

Dann erzeugen wir ein Modul für unsere Applikation zum serverseitigen Laden:src/app/server.module.ts.

import {NgModule} from '@angular/core';
import {ServerModule} from '@angular/platform-server';
import {ModuleMapLoaderModule} from '@nguniversal/module-map-ngfactory-loader';

import {AppModule} from './app.module';
import {AppComponent} from './app.component';

@NgModule({
 imports: [
   AppModule,
   ServerModule,
   ModuleMapLoaderModule
 ],
 bootstrap: [AppComponent],
})
export class AppServerModule {}

Anschließend erzeugen wir eine Stammdatei, um unser AppServerModule fürs Universal Bundle zu exportieren.

export { AppServerModule } from './app/app.server.module';

Jetzt erzeugen wir eine neue Datei tsconfig.server.json mit dem kopierten Content von tsconfig.app.json und ändern das Modul-Format zu commonjs.

{
 "extends": "../tsconfig.json",
 "compilerOptions": {
   "outDir": "../out-tsc/app",
   "baseUrl": "./",
   "module": "commonjs",
   "types": []
 },
 "exclude": [
   "test.ts",
   "**/*.spec.ts"
 ]
}

Dann fügen wir das Extra-Property entryModule hinzu, damit die App weiß, wo sich app.server.module befindet.

{
 "extends": "../tsconfig.json",
 "compilerOptions": {
   "outDir": "../out-tsc/app",
   "baseUrl": "./",
   "module": "commonjs",
   "types": []
 },
 "exclude": [
   "test.ts",
   "**/*.spec.ts"
 ],
 "angularCompilerOptions": {
   "entryModule": "app/app.server.module#AppServerModule"
 }
}

In angular.json wählen wir die Property architect und fügen eine neue Property mit einem Wert hinzu.
 

"architect": {
 ...
 "server": {
   "builder": "@angular-devkit/build-angular:server",
   "options": {
     "outputPath": "dist/server",
     "main": "src/main.server.ts",
     "tsConfig": "src/tsconfig.server.json"
   }
 }
}

Das Bundle zusammenstellen


Zum jetzigen Zeitpunkt haben wir bereits die komplette Vorbereitung und Integration der App mit Universal abgeschlossen. Als nächsten Schritt müssen wir das Bundle für die App zusammenstellen.

$ ng run demo-universal:server

Angular und Universal

Einen Express Server aufbauen
Als letzten Schritt müssen wir das Universal-Bundle bereitstellen.

Dazu erzeugen wir den Dateinamen server.ts als Root-Level unsere Projekts.

import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import {enableProdMode} from '@angular/core';
// Express Engine
import {ngExpressEngine} from '@nguniversal/express-engine';
// Import module map for lazy loading
import {provideModuleMap} from '@nguniversal/module-map-ngfactory-loader';

import * as express from 'express';
import {join} from 'path';

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
const app = express();

const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('./dist/server/main');

// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine('html', ngExpressEngine({
 bootstrap: AppServerModuleNgFactory,
 providers: [
   provideModuleMap(LAZY_MODULE_MAP)
 ]
}));

app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));

// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });

// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser'), {
 maxAge: '1y'
}));

// All regular routes use the Universal engine
app.get('*', (req, res) => {
 res.render('index', { req });
});

// Start up the Node server
app.listen(PORT, () => {
 console.log(`Node Express server listening on http://localhost:${PORT}`);
});

Schließlich stellen wir unser Bundle zusammen, stellen es bereit und erzeugen eine Datei names webpack.server.config.js beim Root-Level des Projekts. 

const path = require('path');
const webpack = require('webpack');

module.exports = {
 entry: {  server: './server.ts' },
 resolve: { extensions: ['.js', '.ts'] },
 target: 'node',
 // this makes sure we include node_modules and other 3rd party libraries
 externals: [/(node_modules|main\..*\.js)/],
 output: {
   path: path.join(__dirname, 'dist'),
   filename: '[name].js'
 },
 module: {
   rules: [
     { test: /\.ts$/, loader: 'ts-loader' }
   ]
 },
 plugins: [
   // Temporary Fix for issue: https://github.com/angular/angular/issues/11580
   // for "WARNING Critical dependency: the request of a dependency is an expression"
   new webpack.ContextReplacementPlugin(
     /(.+)?angular(\\|\/)core(.+)?/,
     path.join(__dirname, 'src'), // location of your src
     {} // a map of your routes
   ),
   new webpack.ContextReplacementPlugin(
     /(.+)?express(\\|\/)(.+)?/,
     path.join(__dirname, 'src'),
     {}
   )
 ]
}

Nun gehen wir zurück zur Datei package.json und fügen untenstehenden Code ein:

"scripts": {
 "build:universal": "npm run build:client-and-server-bundles && npm run webpack:server",
 "serve:universal": "node dist/server.js",
 "build:client-and-server-bundles": "ng build --prod && ng run demo-universal:server",
 "webpack:server": "webpack --config webpack.server.config.js --progress --colors"
}

Jetzt kannst du beginnen, die Universal Applikation zu erstellen und bereitzustellen.

$ npm run build:universal && npm run serve:universal

Viel Spaß!

Achtung
Das hier ist die Minimalverson einer Express-Application, d.h. sie dient rein zu Anschauungszwecken und ist nicht wirklich alltagstauglich. In einem echten Produktumfeld müssen wir sichergehen, dass wir zusätzliche Authentifizierungen und Sicherheitstests eingerichtet haben. Dieser Artikel dient lediglich dazu, die spezifischen Bestandteile aufzuzeigen, die für Universal selbst erforderlich sind. Der Rest kann ganz individuell gestaltet werden!

Als Referenz: https://github.com/angular/angular-cli/wiki/stories-universal-rendering#step-4-setting-up-an-express-server-to-run-our-universal-bundles

PS: Alle, die ein Angular CLI mit Webpack 4 verwenden, müssen den tsloader zur Version 4.2.0 downgraden.

Über den Autor:
Chaiwut Sittiboonta
Chaiwut Sittiboonta
Frontend Entwickler / Standort Chiang Mai

Als Frontend Entwickler ist Ting san der technischen Umsetzung unserer Projekte im Frontend Bereich beteiligt. Vor allem HTML, CSS, JavaScript und Angular gehören dabei zu seinem Repertoire. 

Wie können wir Sie unterstützen?

Kontaktieren Sie uns

Copyright © 2018 BUZZWOO! GmbH & Co. KG