SEO-Freundlich mit Angular CLI 6.0.2 & Universal

SEO-Freundlich mit Angular CLI 6.0.2 & Universal
Ting
Ting, Frontend Development
28 Mai 2018

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-Applikation 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 Webseiten-Inhalte zugreifen und Previews liefern.

Pre-Rendern
Der Cache rendert Webseiten 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/cli@latest

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.

Interessiert?
Kontaktieren
Sie uns!
@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}`);
});
Interessiert?
Kontaktieren
Sie uns!

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

Sven G.

Jetzt Beratungsgespräch vereinbaren:

Sven Gradwohl

Geschäftsführung

+49 711 217258 41

Email schreiben