> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/angular/angular/llms.txt
> Use this file to discover all available pages before exploring further.

# Server-Side Rendering (SSR)

> Implement server-side rendering for improved performance and SEO with Angular Universal

## Overview

Server-Side Rendering (SSR) generates your Angular application's HTML on the server, providing faster initial page loads, better SEO, and improved performance on low-powered devices.

## Benefits of SSR

<CardGroup cols={2}>
  <Card title="Better SEO" icon="magnifying-glass">
    Search engines can crawl fully-rendered pages with content.
  </Card>

  <Card title="Faster First Paint" icon="rocket">
    Users see content faster before JavaScript loads and executes.
  </Card>

  <Card title="Social Sharing" icon="share-nodes">
    Preview images and metadata work correctly on social platforms.
  </Card>

  <Card title="Low-End Devices" icon="mobile">
    Better performance on devices with limited processing power.
  </Card>
</CardGroup>

## Setup

### Adding SSR to Existing Application

Use Angular CLI to add SSR support:

```bash theme={null}
ng add @angular/ssr
```

This command:

* Installs necessary dependencies
* Creates server-side configuration files
* Updates `angular.json` with server build configuration
* Adds server entry point files

### Manual Setup

For more control, set up SSR manually:

```typescript theme={null}
// server.ts
import 'zone.js/node';
import { APP_BASE_HREF } from '@angular/common';
import { CommonEngine } from '@angular/ssr';
import * as express from 'express';
import { fileURLToPath } from 'node:url';
import { dirname, join, resolve } from 'node:path';
import bootstrap from './src/main.server';

const serverDistFolder = dirname(fileURLToPath(import.meta.url));
const browserDistFolder = resolve(serverDistFolder, '../browser');
const indexHtml = join(browserDistFolder, 'index.html');

const app = express();
const commonEngine = new CommonEngine();

// Serve static files
app.get('*.*', express.static(browserDistFolder, {
  maxAge: '1y'
}));

// All regular routes use the Angular engine
app.get('*', (req, res, next) => {
  const { protocol, originalUrl, baseUrl, headers } = req;

  commonEngine
    .render({
      bootstrap,
      documentFilePath: indexHtml,
      url: `${protocol}://${headers.host}${originalUrl}`,
      publicPath: browserDistFolder,
      providers: [
        { provide: APP_BASE_HREF, useValue: baseUrl }
      ],
    })
    .then((html) => res.send(html))
    .catch((err) => next(err));
});

export default app;
```

## Server Configuration

### Main Server Bootstrap

```typescript theme={null}
// main.server.ts
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
import { config } from './app/app.config.server';

const bootstrap = () => bootstrapApplication(AppComponent, config);

export default bootstrap;
```

### Server App Configuration

```typescript theme={null}
// app.config.server.ts
import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
import { provideServerRendering } from '@angular/platform-server';
import { appConfig } from './app.config';

const serverConfig: ApplicationConfig = {
  providers: [
    provideServerRendering()
  ]
};

export const config = mergeApplicationConfig(appConfig, serverConfig);
```

## Rendering Applications

### Using renderApplication

```typescript theme={null}
import { renderApplication } from '@angular/platform-server';
import { AppComponent } from './app/app.component';
import { appConfig } from './app/app.config';

async function render(url: string, document: string): Promise<string> {
  const html = await renderApplication(AppComponent, {
    appId: 'my-app',
    document,
    url,
    providers: [
      ...appConfig.providers
    ]
  });

  return html;
}
```

### Using renderModule (NgModule)

```typescript theme={null}
import { renderModule } from '@angular/platform-server';
import { AppServerModule } from './app/app.server.module';

async function render(url: string, document: string): Promise<string> {
  const html = await renderModule(AppServerModule, {
    document,
    url,
    extraProviders: [
      // Additional server-specific providers
    ]
  });

  return html;
}
```

## Platform-Specific Code

### Detecting Platform

Use platform checks to run code conditionally.

```typescript theme={null}
import { Component, Inject, PLATFORM_ID } from '@angular/core';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';

@Component({
  selector: 'app-platform-aware',
  template: `
    <div>
      <p>Platform: {{ platform }}</p>
      <p *ngIf="isBrowser">Window width: {{ windowWidth }}</p>
    </div>
  `
})
export class PlatformAwareComponent {
  platform: string;
  isBrowser: boolean;
  windowWidth?: number;

  constructor(@Inject(PLATFORM_ID) private platformId: object) {
    this.isBrowser = isPlatformBrowser(platformId);
    this.platform = this.isBrowser ? 'Browser' : 'Server';

    if (this.isBrowser) {
      this.windowWidth = window.innerWidth;
    }
  }
}
```

### Conditional Browser APIs

```typescript theme={null}
import { Component, Inject, PLATFORM_ID, OnInit } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

@Component({
  selector: 'app-local-storage',
  template: `
    <div>
      <input 
        [(ngModel)]="value" 
        (input)="save()"
        placeholder="Type something..."
      >
      <p>Stored value: {{ storedValue }}</p>
    </div>
  `
})
export class LocalStorageComponent implements OnInit {
  value = '';
  storedValue = '';
  private isBrowser: boolean;

  constructor(@Inject(PLATFORM_ID) platformId: object) {
    this.isBrowser = isPlatformBrowser(platformId);
  }

  ngOnInit(): void {
    if (this.isBrowser) {
      this.storedValue = localStorage.getItem('myValue') || '';
      this.value = this.storedValue;
    }
  }

  save(): void {
    if (this.isBrowser) {
      localStorage.setItem('myValue', this.value);
      this.storedValue = this.value;
    }
  }
}
```

## HTTP and Data Transfer

### TransferState

Avoid duplicate HTTP requests by transferring data from server to client.

```typescript theme={null}
import { Component, OnInit } from '@angular/core';
import { 
  makeStateKey, 
  TransferState 
} from '@angular/platform-browser';
import { HttpClient } from '@angular/common/http';

interface User {
  id: number;
  name: string;
  email: string;
}

const USERS_KEY = makeStateKey<User[]>('users');

@Component({
  selector: 'app-users',
  template: `
    <div *ngIf="users">
      <h2>Users</h2>
      <ul>
        <li *ngFor="let user of users">
          {{ user.name }} - {{ user.email }}
        </li>
      </ul>
    </div>
    <p *ngIf="!users">Loading...</p>
  `
})
export class UsersComponent implements OnInit {
  users?: User[];

  constructor(
    private http: HttpClient,
    private transferState: TransferState
  ) {}

  ngOnInit(): void {
    // Check if data exists in transfer state
    const cachedUsers = this.transferState.get(USERS_KEY, null);

    if (cachedUsers) {
      // Use cached data from server
      this.users = cachedUsers;
    } else {
      // Fetch data and store for transfer
      this.http.get<User[]>('/api/users').subscribe(users => {
        this.users = users;
        this.transferState.set(USERS_KEY, users);
      });
    }
  }
}
```

### HTTP Interceptor for TransferState

```typescript theme={null}
import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpRequest,
  HttpHandler,
  HttpEvent,
  HttpResponse
} from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
import { TransferState, makeStateKey } from '@angular/platform-browser';

@Injectable()
export class TransferStateInterceptor implements HttpInterceptor {
  constructor(private transferState: TransferState) {}

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    // Only cache GET requests
    if (req.method !== 'GET') {
      return next.handle(req);
    }

    const key = makeStateKey(req.url);
    const cachedResponse = this.transferState.get(key, null);

    if (cachedResponse) {
      // Remove from transfer state after using
      this.transferState.remove(key);
      return of(new HttpResponse({ body: cachedResponse, status: 200 }));
    }

    return next.handle(req).pipe(
      tap(event => {
        if (event instanceof HttpResponse) {
          this.transferState.set(key, event.body);
        }
      })
    );
  }
}
```

## Pre-rendering (SSG)

Generate static pages at build time for even better performance.

### Configuration

```typescript theme={null}
// angular.json
{
  "projects": {
    "my-app": {
      "architect": {
        "prerender": {
          "builder": "@angular-devkit/build-angular:prerender",
          "options": {
            "routes": [
              "/",
              "/about",
              "/contact",
              "/blog/post-1",
              "/blog/post-2"
            ]
          },
          "configurations": {
            "production": {
              "browserTarget": "my-app:build:production",
              "serverTarget": "my-app:server:production"
            }
          }
        }
      }
    }
  }
}
```

### Dynamic Route Discovery

```typescript theme={null}
// routes.txt or routes generator
import { writeFileSync } from 'fs';
import { join } from 'path';

interface BlogPost {
  slug: string;
}

async function generateRoutes(): Promise<void> {
  // Fetch dynamic routes from API or database
  const posts: BlogPost[] = await fetchBlogPosts();
  
  const routes = [
    '/',
    '/about',
    '/contact',
    ...posts.map(post => `/blog/${post.slug}`)
  ];

  // Write routes to file
  const routesFile = join(__dirname, 'routes.txt');
  writeFileSync(routesFile, routes.join('\n'));
}

async function fetchBlogPosts(): Promise<BlogPost[]> {
  // Fetch from your API or database
  return [];
}

generateRoutes();
```

## SEO Optimization

### Meta Tags Service

```typescript theme={null}
import { Injectable } from '@angular/core';
import { Meta, Title } from '@angular/platform-browser';

@Injectable({ providedIn: 'root' })
export class SeoService {
  constructor(
    private meta: Meta,
    private title: Title
  ) {}

  updateMetaTags(config: {
    title: string;
    description: string;
    image?: string;
    url?: string;
    type?: string;
  }): void {
    // Update title
    this.title.setTitle(config.title);

    // Update meta tags
    this.meta.updateTag({ name: 'description', content: config.description });
    
    // 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: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 });
    }

    // Twitter Card tags
    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 });
    }
  }
}
```

### Using SEO Service

```typescript theme={null}
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { SeoService } from './seo.service';

interface BlogPost {
  title: string;
  description: string;
  image: string;
  slug: string;
}

@Component({
  selector: 'app-blog-post',
  template: `
    <article *ngIf="post">
      <h1>{{ post.title }}</h1>
      <img [src]="post.image" [alt]="post.title">
      <p>{{ post.description }}</p>
    </article>
  `
})
export class BlogPostComponent implements OnInit {
  post?: BlogPost;

  constructor(
    private route: ActivatedRoute,
    private seo: SeoService
  ) {}

  ngOnInit(): void {
    this.route.data.subscribe(data => {
      this.post = data['post'];
      
      if (this.post) {
        this.seo.updateMetaTags({
          title: this.post.title,
          description: this.post.description,
          image: this.post.image,
          url: `https://example.com/blog/${this.post.slug}`,
          type: 'article'
        });
      }
    });
  }
}
```

## Best Practices

<CardGroup cols={2}>
  <Card title="Use TransferState" icon="exchange">
    Avoid duplicate HTTP requests by transferring data from server to client.
  </Card>

  <Card title="Lazy Load Heavy Components" icon="spinner">
    Defer loading of non-critical components to improve initial render time.
  </Card>

  <Card title="Handle Platform Differences" icon="code-branch">
    Always check platform before accessing browser-only APIs.
  </Card>

  <Card title="Optimize Images" icon="image">
    Use responsive images and lazy loading for better performance.
  </Card>
</CardGroup>

<Warning>
  Avoid using browser-specific APIs like `window`, `document`, `localStorage` without platform checks. These will cause errors during server-side rendering.
</Warning>

## Additional Resources

* [Hydration](/advanced/hydration)
* [Angular Universal Documentation](https://angular.dev/guide/ssr)
* [SEO Best Practices](https://angular.dev/guide/ssr#seo)
