
Angular Ssr
Implement and debug Angular server-side rendering with hydration-safe patterns, SEO, auth, caching, and performance.
Install
npx skills add https://github.com/analogjs/angular-skills --skill angular-ssrWhat is this skill?
- Hydration mismatch fixes using signals, `afterNextRender`, and `ngSkipHydration`
- SEO-oriented SSR patterns alongside authentication flows that work on server and client
- Caching strategies and SSR-aware error handling for production-shaped apps
- Performance optimization guidance tied to Angular hydration providers
- Six documented topic areas: hydration, SEO, auth, caching, errors, and performance
Adoption & trust: 3.1k installs on skills.sh; 592 GitHub stars; 1/3 security scanners passed (skills.sh audits).
Recommended Skills
Frontend Designanthropics/skills
Vercel React Best Practicesvercel-labs/agent-skills
Remotion Best Practicesremotion-dev/skills
Vercel Composition Patternsvercel-labs/agent-skills
Develop Userscriptsxixu-me/skills
Next Best Practicesvercel-labs/next-skills
Journey fit
Primary fit
SSR work starts while building the Angular UI layer, before you treat shipping and discoverability as separate concerns. Hydration, components, and `provideClientHydration` configuration are frontend architecture tasks in the Build phase.
Common Questions / FAQ
Is Angular Ssr safe to install?
skills.sh reports 1 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.
SKILL.md
READMESKILL.md - Angular Ssr
# Angular SSR Patterns ## Table of Contents - [Hydration Debugging](#hydration-debugging) - [SEO Optimization](#seo-optimization) - [Authentication with SSR](#authentication-with-ssr) - [Caching Strategies](#caching-strategies) - [Error Handling](#error-handling) - [Performance Optimization](#performance-optimization) ## Hydration Debugging ### Common Hydration Mismatches ```typescript // Problem: Different content on server vs client @Component({ template: `<p>Current time: {{ currentTime }}</p>`, }) export class Time { // BAD: Different value on server and client currentTime = new Date().toLocaleTimeString(); } // Solution: Use afterNextRender or skip SSR @Component({ template: `<p>Current time: {{ currentTime() }}</p>`, }) export class Time { currentTime = signal(''); constructor() { afterNextRender(() => { this.currentTime.set(new Date().toLocaleTimeString()); }); } } ``` ### Skip Hydration for Dynamic Content ```typescript @Component({ template: ` <!-- Skip hydration for this subtree --> <div ngSkipHydration> <app-dynamic-widget /> </div> `, }) export class Page {} ``` ### Debug Hydration Issues ```typescript // Enable hydration debugging in development import { provideClientHydration, withNoDomReuse } from '@angular/platform-browser'; export const appConfig: ApplicationConfig = { providers: [ provideClientHydration( // Disable DOM reuse to see hydration errors clearly ...(isDevMode() ? [withNoDomReuse()] : []) ), ], }; ``` ## SEO Optimization ### Meta Tags Service ```typescript import { Injectable, inject } from '@angular/core'; import { Meta, Title } from '@angular/platform-browser'; import { DOCUMENT } from '@angular/common'; @Injectable({ providedIn: 'root' }) export class Seo { private meta = inject(Meta); private title = inject(Title); private document = inject(DOCUMENT); updateMetaTags(config: { title: string; description: string; image?: string; url?: string; type?: string; }) { // Basic meta this.title.setTitle(config.title); this.meta.updateTag({ name: 'description', content: config.description }); // Open Graph this.meta.updateTag({ property: 'og:title', content: config.title }); this.meta.updateTag({ property: 'og:description', content: config.description }); this.meta.updateTag({ property: 'og:type', content: config.type || 'website' }); if (config.image) { this.meta.updateTag({ property: 'og:image', content: config.image }); } if (config.url) { this.meta.updateTag({ property: 'og:url', content: config.url }); this.updateCanonicalUrl(config.url); } // Twitter Card this.meta.updateTag({ name: 'twitter:card', content: 'summary_large_image' }); this.meta.updateTag({ name: 'twitter:title', content: config.title }); this.meta.updateTag({ name: 'twitter:description', content: config.description }); if (config.image) { this.meta.updateTag({ name: 'twitter:image', content: config.image }); } } private updateCanonicalUrl(url: string) { let link: HTMLLinkElement | null = this.document.querySelector('link[rel="canonical"]'); if (!link) { link = this.document.createElement('link'); link.setAttribute('rel', 'canonical'); this.document.head.appendChild(link); } link.setAttribute('href', url); } setJsonLd(data: object) { let script: HTMLScriptElement | null = this.document.querySelector('script[type="application/ld+json"]'); if (!script) { script = this.document.createElement('script'); script.type = 'application/ld+json'; this.document.head.appendChild(script); } script.textContent = JSON.stringify(data); } } // Usage in component @Component({...}) export class Product { private seo = inject(Seo); product = input.required<Product>(); constructor() { effect(() => {