Marketing & Social
SEO & Social Sharing
Being found is everything. From index.html to dynamic social media images: This is how you make Google understand your app.
In Angular SEO, we distinguish between two levels:
1. The Foundation: Things that stay constant (viewport, language).
2. The Dynamic Content: Titles, images, and descriptions that change per route.
Thanks to SSR (Server Side Rendering), we can serve both perfectly. Bots no longer stare at a "Loading..." screen but receive fully hydrated HTML instantly.
1. The Foundation (index.html)
Before writing any TypeScript, the basics must be solid. These settings in src/index.html are mandatory for ranking. Never forget the viewport tag; otherwise, Google will penalize your site as "not mobile-friendly."
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Aden Library</title>
<base href="/">
<!-- Important for mobile ranking! -->
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico">
<!-- Canonical prevents duplicate content issues -->
<link rel="canonical" href="https://aden-library.com">
</head>
<body>
<app-root></app-root>
</body>
</html>Language Setting: Ensure the tag has the correct language (e.g., lang='en'). This is crucial for accessibility tools and local search results.
2. The Dynamic Part
The index.html is static. But when a user shares a profile link, we want to show their specific avatar and name. The preview below demonstrates what our SeoService generates.
SEO Service Input
Simuliere hier die Daten, die aus deiner API kommen.
Social Media Preview
So sieht dein Link auf WhatsApp / LinkedIn aus:
3. The SeoService (All-in-One)
I use a central service that handles three major tasks:
1. Title & Description for Google Search.
2. Open Graph Tags for rich previews on WhatsApp/LinkedIn.
3. Canonical URLs to prevent duplicate content issues.
import { Injectable, inject } from '@angular/core';
import { Title, Meta } from '@angular/platform-browser';
@Injectable({ providedIn: 'root' })
export class SeoService {
private titleService = inject(Title);
private metaService = inject(Meta);
updateSeo(config: SeoConfig) {
const fullTitle = `${config.title} | Aden Library`;
this.titleService.setTitle(fullTitle);
// Standard Meta Tags
this.setMeta('name', 'description', config.description);
// Open Graph (WhatsApp, LinkedIn, Discord)
this.setMeta('property', 'og:title', fullTitle);
this.setMeta('property', 'og:description', config.description);
if (config.image) {
this.setMeta('property', 'og:image', config.image);
}
}
private setMeta(attr: string, key: string, content: string) {
this.metaService.updateTag({ [attr]: key, content: content });
}
}4. Automation
Manually calling ngOnInit in every static component (Home, Imprint, Login) is tedious and error-prone. That's why we automate it.
Step A: Data in the Route
We define titles and descriptions directly in the route configuration. This keeps our components clean and dumb.
// app.routes.ts
import { Routes } from '@angular/router';
export const routes: Routes = [
{
path: 'home',
component: HomeComponent,
// Define SEO data directly in the route config
data: {
title: 'Home - UI Components',
description: 'The best library for Angular components.'
}
},
{
path: 'imprint',
loadComponent: ... ,
data: {
title: 'Imprint',
description: 'Legal information and contact.'
}
}
];Step B: The Auto-Pilot (AppComponent)
In app.component.ts, we listen to every navigation event globally. If the active route contains SEO data, this code automatically feeds our SeoService. Zero manual work required.
// app.component.ts
import { Router, NavigationEnd, ActivatedRoute } from '@angular/router';
import { filter, map, mergeMap } from 'rxjs/operators';
export class AppComponent implements OnInit {
private router = inject(Router);
private activatedRoute = inject(ActivatedRoute);
private seoService = inject(SeoService);
ngOnInit() {
this.router.events.pipe(
// 1. Wait for navigation to finish
filter(event => event instanceof NavigationEnd),
// 2. Get current route
map(() => this.activatedRoute),
// 3. Traverse down to the active child route
map(route => {
while (route.firstChild) route = route.firstChild;
return route;
}),
// 4. Extract data property
mergeMap(route => route.data)
).subscribe(data => {
// If SEO data is present -> Set it automatically
if (data['title']) {
this.seoService.updateSeo({
title: data['title'],
description: data['description'],
image: data['image']
});
}
});
}
}5. Dynamic Content (Profiles)
For profiles or product details, the route config doesn't know the specific data yet. Here, we leave the data object empty and manually call the service once the API response arrives.
// member-profile.ts
ngOnInit() {
// We cannot use route data here because the username is dynamic.
// SSR waits for this API call to finish!
this.userService.getProfile('johndoe').subscribe(user => {
// Overwrite metadata with real user info
this.seo.updateSeo({
title: `${user.firstName} ${user.lastName}`,
description: `Check out the profile of ${user.firstName}.`,
image: user.avatarUrl
});
});
}SSR Queue: Angular creates a queue on the server. It waits until all HTTP requests in ngOnInit are completed before generating the HTML. This ensures your dynamic meta tags are fully rendered when the bot arrives.
6. The Ultimate SEO Checklist
- SSR enabled? Without it, bots often see a blank page (see SSR Guide).
- Canonical Tags? Essential to avoid duplicate content penalties.
- Sitemap.xml? Ensures Google discovers all deep links.
- Robots.txt? Keep admin areas private.
- Structured Data (JSON-LD)? For rich snippets like ratings or breadcrumbs.
Tip: Check out the separate guides for Sitemap and Robots.txt in the library.