Sitemap

Have an Angular Website but Struggling with SEO & GTmetrix A-Grade? …. Here’s the Solution!

6 min readMar 28, 2025

--

Build an SEO-Friendly Angular Website and Achieve an A-Grade on GTmetrix — With Animations & Particles.js

Introduction

Many developers believe Angular is not ideal for SEO compared to React, but with the right strategies, you can make an Angular site SEO-friendly and high-performing. This guide explains how we optimized an Angular 17 application to achieve an A-grade on GTmetrix, even while using Particles.js and heavy animations.

Press enter or click to view image in full size

Why Server-Side Rendering (SSR) is Essential for SEO

Angular applications typically render content on the client-side, which makes it difficult for search engines to crawl. Server-Side Rendering (SSR) solves this by rendering HTML on the server before sending it to the client, significantly improving SEO and initial load speed.

Challenges in Angular 17 SSR

  1. Handling browser-specific APIs in a server environment.
  2. Dynamically updating meta tags for SEO
  3. Implementing server-side caching
  4. Generating and optimizing a sitemap.xml
  5. Pre-rendering routes for faster loading
  6. Lazy loading images to improve performance
  7. Optimizing sites with Particles.js and complex animations

1. Handling Browser Scope in Angular SSR

In Angular SSR, code that relies on browser-specific APIs (e.g., window, document, localStorage) can cause errors because these APIs are not available on the server.

Solution

Use Angular’s isPlatformBrowser utilities to execute browser-specific code conditionally. It will throw errors because they don’t exist in the Node.js environment where the server runs.

To avoid these errors, you need to conditionally execute browser-specific code only when the application is running in the browser.

import { Injectable, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';


@Injectable({
providedIn: 'root',
})
export class BrowserService {
constructor(@Inject(PLATFORM_ID) private platformId: Object) {}


isBrowser(): boolean {
return isPlatformBrowser(this.platformId);
}
}

Usage:

  • Accessing local storage
if (this.browserService.isBrowser()) {
const userData = localStorage.getItem('user');
}
  • Accessing window
if (this.browserService.isBrowser()) {
window.scrollTo(0, 0); // Scroll to the top of the page
}
  • Using document
if (this.browserService.isBrowser()) {
const element = document.getElementById('myElement');
if (element) {
element.style.color = 'red';
}
}

2. Updating Meta Tags Dynamically in SSR

Meta tags are essential for SEO. We dynamically injected metadata into the rendered HTML before serving it.

i. Handling Dynamic Metadata Injection

The server retrieves metadata from an external API and injects it into the rendered HTML before sending the response.

ii. Optimized Asynchronous Operations

To reduce latency, HTML rendering and metadata fetching are performed concurrently using Promise.all.

iii. Error Handling and Fallbacks

  • If metadata API calls fail, the application falls back to default values.
  • Ensures that HTML rendering proceeds even if metadata is unavailable.

Example:

How the server.ts file dynamically updates meta tags before sending the final HTML response to the client using res.send(finalHtml);.

server.get('*', async (req, res, next) => {
const { protocol, originalUrl, baseUrl, headers } = req;
const page = getPageIDByUrl.find((item) => item.slug === req.url);
try {
// Render HTML and fetch metadata concurrently
const [renderedHtml, metadataResponse] = await Promise.all([
commonEngine.render({
bootstrap,
documentFilePath: indexHtml,
url: `${protocol}://${headers.host}${originalUrl}`,
publicPath: browserDistFolder,
providers: [{ provide: APP_BASE_HREF, useValue: baseUrl }],
}),
axios
.post(
`${environment.host}/page/details`,
{ slug: page.slug },
{
headers: {
'Content-Type': 'application/json',
},
}
)
.catch((error) => {
return null; // Fallback to no metadata
}),
]);


if (!renderedHtml) {
res.status(500).send('Error rendering HTML.');
return;
}
let finalHtml = renderedHtml;
if (metadataResponse && metadataResponse.data) {
let metadata = metadataResponse.response;
finalHtml = finalHtml
.replace(
/<title>.*<\/title>/,
`<title>${metadata.page_title || ''}</title>`
)
.replace(
'</head>',
`<meta name="keywords" content="${metadata.page_keyword || ''}">
<meta name="description" content="${
metadata.page_description || ''
}">
</head>`
);
}
res.send(finalHtml);
} catch (error) {
next(error); // Forward error to centralized error handler
}
});

3. Implementing Server-Side Caching

To reduce rendering time, we used cache-manager to store HTML responses.

Installation:

npm i cache-manager

i. We use cache-manager to store HTML responses:

import { createCache } from "cache-manager";
const cache = createCache({
ttl: 10000, // Cache TTL in milliseconds
refreshThreshold: 3000,
});

ii. Check Cache Before Rendering

Before processing a request, check if it’s already cached:

server.use(async (req, res, next) => {
const { protocol, originalUrl, headers } = req;
const cacheKey = `${protocol}://${headers.host}${originalUrl}`;
const cachedHtml = await cache.get(cacheKey);
if (cachedHtml) {
console.log(`Cache hit: ${cacheKey}`);
res.send(cachedHtml);
} else next();
});

4. Implementing sitemap.xml

A sitemap.xml file helps search engines efficiently index pages.

Example:

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
<loc>https://example.com/</loc>
<lastmod>2025-03-25</lastmod>
<priority>1.0</priority>
</url>
<url>
<loc>https://example.com/about</loc>
<lastmod>2025-03-20</lastmod>
<priority>0.8</priority>
</url>
</urlset>

5. Pre-rendering Routes

Pre-rendering generates static HTML files for key routes, improving performance.

A routes.txt file is used in pre-rendering to specify which routes should be pre-rendered.

Setup Steps:

  1. Create a routes.txt file and list all pages that should be pre-rendered (one per line).
  2. Update the angular.json to include the routesFile option.
  3. Deploy the pre-rendered pages for faster initial load and better SEO.
"prerender": {
"discoverRoutes": false,
"routesFile": "src/routes.txt"
}

6. Lazy Loading Images

Lazy loading delays loading images until needed, improving performance.

i. Create a directive for lazy loading images:

import { isPlatformBrowser } from '@angular/common';
import { Directive,ElementRef,Inject,Input,inject,PLATFORM_ID,Output,EventEmitter} from '@angular/core';
@Directive({
selector: '[lazyLoadSrc]',
standalone: true,
})
export class LazyLoadSrcDirective {
private _el = inject(ElementRef);
private platformId!: Object;
@Input() lazyLoadSrc = '';
@Output() fullyLoaded = new EventEmitter<Event>();
constructor(@Inject(PLATFORM_ID) platformId: Object) {
this.platformId = platformId;
}
ngOnInit(): void {
const img = this._el.nativeElement as HTMLImageElement;
if (isPlatformBrowser(this.platformId)) {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
img.src = this.lazyLoadSrc;
observer.unobserve(img);
img.onload = (event: Event) => {
this.fullyLoaded.emit(event);
};
}
});
});
observer.observe(img);
} else {
img.src = this.lazyLoadSrc;
}
}
}

ii. Use the directive in the template:

<img [lazyLoadSrc]="imageSrc" alt="Lazy loaded image">

7. Handling Particles.js and Heavy Animations

Particles.js and complex animations can slow down a site. We optimized animations by:

While integrating animations into this website it impacts performance, leading to SEO issues. While using @tsparticles/angular and @tsparticles/slim in our project, we encountered performance challenges reflected in our GTmetrix reports.

The Problem: Performance and SEO Issues

When we initially implemented tsParticles, we noticed the following issues in our GTmetrix reports:

  1. Increased JavaScript Execution Time — Heavy particle animations caused longer page load times.
  2. Higher Total Blocking Time (TBT) — tsParticles script execution was delaying interactive elements.
  3. Decreased First Contentful Paint (FCP) — Render-blocking resources led to slower initial loading.
  4. Lower SEO Score — Poor page speed negatively impacted search engine rankings.

Our Optimization

  • Disabled Full-Screen Mode: Prevented tsParticles from covering the entire viewport unnecessarily.
  • Limited FPS to 30: Reduced CPU load while maintaining smooth animations.
  • Reduced Particle Count: Lowered from default values to 80, balancing visuals and performance.
  • Controlled Particle Size: Avoided random sizes to reduce rendering complexity.
  • Removed Interactivity Options: Reduced event listeners and calculations, improving page responsiveness.
  • Disabled Retina Detection: Prevented high-density displays from increasing particle count dynamically.

Example: Optimized Particles.js Configuration

{
fullScreen: { enable: false },
fpsLimit: 30,
particles: {
number: {
value: 80, // Reduced particle count
density: { enable: true, area: 800 },
},
color: { value: '#ffffff' },
shape: { type: 'circle' },
opacity: {
value: 0.6,
random: false,
animation: { enable: false },
},
size: {
value: 4, // Controlled size for better optimization
random: false,
},
move: {
enable: true,
speed: 0.5,
direction: MoveDirection.none,
outModes: { default: OutMode.out },
straight: false,
},
links: {
enable: true,
distance: 150,
color: '#ffffff',
opacity: 0.5,
width: 1.5,
},
},
detectRetina: false,
}

7. AOS animation SEO Issues

We integrated AOS (Animate On Scroll) in our project to enhance the user experience with smooth animations. After running a GTmetrix performance test, we noticed a drop in our SEO and Core Web Vitals scores.

We found that AOS was affecting our page speed, LCP (Largest Contentful Paint), and Cumulative Layout Shift (CLS).

We Fixed AOS SEO Issues — Configuration Changes:

After debugging, we found that modifying specific AOS configurations resolve most of these issues without removing animations.

AOS.init({
delay: 50, // Minimal delay for animations
duration: 600, // Keep animations smooth but fast
easing: "ease",
once: true, // Run animation only once (prevents re-execution)
});

GTmetrix Performance Report

After these optimizations, our Angular site achieved an A-grade on GTmetrix. Improvements included:

  • Faster load times
  • Lower server load
  • Better SEO rankings
  • Smoother user experience
Press enter or click to view image in full size

Conclusion

It is absolutely possible to make an Angular application SEO-friendly and high-performing. By leveraging SSR, optimizing metadata, caching, pre-rendering, lazy loading images, and handling heavy animations, we achieved top-tier performance.

If you’re looking for expert help in building a high-performance, SEO-friendly Angular website, contact us today! 🚀

Contributors

This article was written by Abhisek Dhua (Senior Developer) & Mahadev Maity (Frontend Lead)

Get in touch with MSS for your Generative AI use case implementation and other project development tasks.

--

--