How to Add SEO to Your Angular App (So Google Actually Finds It)
Your Angular app is invisible to Google. Here's a 6-step fix: enable SSR, add meta tags, make links crawlable, submit a sitemap, and add structured data.
How to Add SEO to Your Angular App (So Google Actually Finds It)
You built an Angular app. It looks great. It works perfectly. But when you search for it on Google — nothing. It's like your app doesn't exist.
Here's why: Angular apps are client-side rendered by default. When Googlebot visits your site, it receives an empty HTML shell — literally just <app-root></app-root> with zero content. Google has to queue your page for JavaScript rendering, which can take hours to weeks. And sometimes it fails entirely.
The fix isn't complicated, but you need to do it in the right order. Here are 6 steps, ranked by SEO impact, to make your Angular app visible to Google.
Step 1: Enable Server-Side Rendering with @angular/ssr
Why this matters: Without SSR, Google receives an empty page. Every other SEO fix in this guide is pointless until you solve this.
What Google sees without SSR:
<!doctype html>
<html>
<head><title></title></head>
<body>
<app-root></app-root>
<script src="main.js"></script>
</body>
</html>
No title. No content. No headings. Nothing for Google to index.
How to fix it:
ng add @angular/ssr
This command sets up everything — server.ts, app.config.server.ts, and the build configuration. After running it, rebuild and deploy your app.
Verify it's working:
curl -s https://your-domain.com | head -50
You should now see your actual page content — headings, text, meta tags — in the raw HTML response. If you still see an empty <app-root>, SSR isn't configured correctly.
Step 2: Set Title, Meta Description, and Canonical URL on Every Route
Why this matters: Even with SSR enabled, Google needs to know what each page is about. Without unique titles and descriptions, Google either guesses (badly) or ignores your pages.
The common mistake: Setting meta tags in ngAfterViewInit or behind isPlatformBrowser checks. Both approaches exclude meta tags from the server-rendered HTML — which is exactly what Google reads.
How to fix it — create a reusable SEO service:
import { Injectable } from '@angular/core';
import { Title, Meta } from '@angular/platform-browser';
import { DOCUMENT } from '@angular/common';
import { inject } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class SeoService {
private title = inject(Title);
private meta = inject(Meta);
private doc = inject(DOCUMENT);
updateTags(config: {
title: string;
description: string;
url: string;
image?: string;
}): void {
this.title.setTitle(config.title);
this.meta.updateTag({ name: 'description', content: config.description });
// Canonical URL
let link: HTMLLinkElement | null =
this.doc.querySelector('link[rel="canonical"]');
if (!link) {
link = this.doc.createElement('link');
link.setAttribute('rel', 'canonical');
this.doc.head.appendChild(link);
}
link.setAttribute('href', config.url);
// Open Graph tags
this.meta.updateTag({ property: 'og:title', content: config.title });
this.meta.updateTag({ property: 'og:description', content: config.description });
this.meta.updateTag({ property: 'og:url', content: config.url });
if (config.image) {
this.meta.updateTag({ property: 'og:image', content: config.image });
}
}
}
Use it in every route component — in ngOnInit, not ngAfterViewInit:
@Component({
selector: 'app-about',
standalone: true,
templateUrl: './about.component.html',
})
export class AboutComponent implements OnInit {
private seo = inject(SeoService);
ngOnInit(): void {
this.seo.updateTags({
title: 'About Us | Your App',
description: 'Learn about our team and mission.',
url: 'https://your-domain.com/about',
});
}
}
Step 3: Replace Programmatic Navigation with RouterLink
Why this matters: Googlebot discovers pages by following <a href="..."> links in your HTML. If you navigate using (click) handlers with router.navigate(), there's no <a> tag in the HTML — Googlebot can't click buttons.
Before (invisible to Google):
<div (click)="goToProducts()">View Products</div>
goToProducts(): void {
this.router.navigate(['/products']);
}
After (crawlable by Google):
<a [routerLink]="['/products']">View Products</a>
The [routerLink] directive renders a standard <a href="/products"> element in the HTML. Googlebot follows it and discovers your /products page.
Audit your app: Search your codebase for router.navigate and (click) handlers that trigger navigation. Replace every one with [routerLink].
Step 4: Generate and Submit an XML Sitemap
Why this matters: Angular doesn't generate a sitemap automatically. If a page has no inbound links pointing to it (common with lazy-loaded routes), Google will never discover it — unless you tell Google about it via a sitemap.
Create a sitemap.xml in your src/ folder:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://your-domain.com/</loc>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>https://your-domain.com/about</loc>
<changefreq>monthly</changefreq>
<priority>0.8</priority>
</url>
<url>
<loc>https://your-domain.com/products</loc>
<changefreq>weekly</changefreq>
<priority>0.9</priority>
</url>
<!-- Add every route your app has -->
</urlset>
Add it to your angular.json assets array so it gets copied to the build output:
"assets": [
"src/favicon.ico",
"src/assets",
"src/sitemap.xml"
]
Submit it in Google Search Console under Sitemaps. Google will start crawling the listed URLs within hours.
Step 5: Add JSON-LD Structured Data
Why this matters: Structured data makes your pages eligible for rich results in Google — FAQ dropdowns, breadcrumbs, how-to carousels. These get significantly more clicks than plain blue links.
The common mistake: Injecting JSON-LD via document.createElement('script'). DOM-injected scripts are not serialized during Angular SSR — they won't appear in the HTML Google receives.
The correct approach — put JSON-LD directly in your component template:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Organization",
"name": "Your App",
"url": "https://your-domain.com",
"logo": "https://your-domain.com/assets/logo.png",
"description": "Your app description here"
}
</script>
For dynamic JSON-LD (e.g., product pages), bind the content in the component:
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
@Component({
selector: 'app-product',
standalone: true,
template: `
<div [innerHTML]="jsonLd"></div>
<!-- rest of template -->
`,
})
export class ProductComponent implements OnInit {
jsonLd!: SafeHtml;
private sanitizer = inject(DomSanitizer);
ngOnInit(): void {
const schema = {
'@context': 'https://schema.org',
'@type': 'Product',
name: 'Product Name',
description: 'Product description',
};
this.jsonLd = this.sanitizer.bypassSecurityTrustHtml(
`<script type="application/ld+json">${JSON.stringify(schema)}</script>`
);
}
}
Validate your structured data using Google's Rich Results Test after deployment.
Step 6: Add Open Graph Meta Tags
Why this matters: OG tags control how your links look when shared on social media. Good previews get more clicks, which drives referral traffic and backlinks — both of which indirectly boost your Google rankings.
If you followed Step 2 and used the SeoService, OG tags are already handled. Just make sure you're passing the image parameter:
this.seo.updateTags({
title: 'Product Name | Your App',
description: 'The best product for your needs.',
url: 'https://your-domain.com/products/1',
image: 'https://your-domain.com/assets/product-1.jpg',
});
Common Issues and How to Debug Them
SSR is enabled but Google still shows a blank page:
Run curl -s https://your-domain.com and check if the HTML contains your content. If not, your SSR deployment may not be configured correctly — make sure your server runs the SSR bundle, not just serves the static index.html.
Meta tags appear in browser but not in curl output:
You're setting meta tags in ngAfterViewInit or behind an isPlatformBrowser guard. Move them to ngOnInit without platform checks.
Some pages aren't getting indexed: Check Google Search Console's URL Inspection tool. If Google hasn't discovered the URL, it's likely not linked from any crawlable page and not in your sitemap. Add it to both.
JSON-LD not appearing in page source:
You're injecting it via JavaScript DOM manipulation. Move JSON-LD into your component template or use the DomSanitizer approach from Step 5.
Summary
Getting your Angular app on Google comes down to one core principle: Google needs to see your content in the raw HTML response. Without SSR, your app is invisible. With SSR enabled, you then need to make sure every page has unique metadata, crawlable links, a sitemap, and structured data.
Here's the priority order:
- Enable SSR — the foundation everything else depends on
- Add meta tags and canonical URLs in
ngOnInit - Use
[routerLink]instead ofrouter.navigate() - Create and submit an XML sitemap
- Add JSON-LD structured data in templates
- Set Open Graph tags for social sharing
Want to see how Google currently views your pages? Scan What's Ranking to find out what's working and what's missing.
FAQ
Does Angular support SSR out of the box?
Yes. Since Angular 17, SSR is a first-class feature via @angular/ssr. Run ng add @angular/ssr to enable it on an existing project. It configures server-side rendering, sets up server.ts, and updates your build configuration automatically.
Why are my Angular meta tags not showing up in Google?
Most likely you're setting them in ngAfterViewInit or wrapping them in isPlatformBrowser checks. Both approaches exclude meta tags from the server-rendered HTML. Move your Title and Meta service calls to ngOnInit without platform guards.
Do I need a sitemap for my Angular app?
Yes. Angular doesn't generate sitemaps automatically, and lazy-loaded routes have no HTML links pointing to them by default. Without a sitemap, Google may never discover those pages. Create a sitemap.xml listing all your routes and submit it via Google Search Console.
How long does it take for Google to index my Angular app after adding SSR?
Typically 1-2 weeks after deploying SSR changes. You can speed this up by submitting your sitemap and requesting indexing of specific URLs through Google Search Console's URL Inspection tool.
Should I use prerendering (SSG) or server-side rendering (SSR) for Angular SEO?
Use prerendering (SSG) for pages that don't change often — landing pages, about pages, blog posts. Use SSR for dynamic pages that change per request — user dashboards, search results, product pages with real-time pricing. Both produce full HTML that Google can index immediately.