Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    analogjs

    angular-ssr

    analogjs/angular-ssr
    Coding
    238
    1 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Implement server-side rendering and hydration in Angular v20+ using @angular/ssr. Use for SSR setup, hydration strategies, prerendering static pages, and handling browser-only APIs.

    SKILL.md

    Angular SSR

    Implement server-side rendering, hydration, and prerendering in Angular v20+.

    Setup

    Add SSR to Existing Project

    ng add @angular/ssr
    

    This adds:

    • @angular/ssr package
    • server.ts - Express server
    • src/main.server.ts - Server bootstrap
    • src/app/app.config.server.ts - Server providers
    • Updates angular.json with SSR configuration

    Project Structure

    src/
    ├── app/
    │   ├── app.config.ts          # Browser config
    │   ├── app.config.server.ts   # Server config
    │   └── app.routes.ts
    ├── main.ts                     # Browser bootstrap
    ├── main.server.ts              # Server bootstrap
    server.ts                       # Express server
    

    Configuration

    app.config.server.ts

    import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
    import { provideServerRendering } from '@angular/platform-server';
    import { provideServerRoutesConfig } from '@angular/ssr';
    import { appConfig } from './app.config';
    import { serverRoutes } from './app.routes.server';
    
    const serverConfig: ApplicationConfig = {
      providers: [
        provideServerRendering(),
        provideServerRoutesConfig(serverRoutes),
      ],
    };
    
    export const config = mergeApplicationConfig(appConfig, serverConfig);
    

    Server Routes Configuration

    // app.routes.server.ts
    import { RenderMode, ServerRoute } from '@angular/ssr';
    
    export const serverRoutes: ServerRoute[] = [
      {
        path: '',
        renderMode: RenderMode.Prerender, // Static at build time
      },
      {
        path: 'products',
        renderMode: RenderMode.Prerender,
      },
      {
        path: 'products/:id',
        renderMode: RenderMode.Server, // Dynamic SSR
      },
      {
        path: 'dashboard',
        renderMode: RenderMode.Client, // Client-only (SPA)
      },
      {
        path: '**',
        renderMode: RenderMode.Server,
      },
    ];
    

    Render Modes

    Mode Description Use Case
    RenderMode.Prerender Static HTML at build time Marketing pages, blogs
    RenderMode.Server Dynamic SSR per request User-specific content
    RenderMode.Client Client-side only (SPA) Authenticated dashboards

    Hydration

    Default Hydration

    Hydration is enabled by default with provideClientHydration():

    // app.config.ts
    import { provideClientHydration } from '@angular/platform-browser';
    
    export const appConfig: ApplicationConfig = {
      providers: [
        provideClientHydration(),
        // ...
      ],
    };
    

    Incremental Hydration

    Defer hydration of specific components:

    @Component({
      template: `
        <!-- Hydrate when visible -->
        @defer (hydrate on viewport) {
          <app-comments [postId]="postId" />
        } @placeholder {
          <div class="comments-placeholder">Loading comments...</div>
        }
        
        <!-- Hydrate on interaction -->
        @defer (hydrate on interaction) {
          <app-interactive-chart [data]="chartData" />
        }
        
        <!-- Hydrate on idle -->
        @defer (hydrate on idle) {
          <app-recommendations />
        }
        
        <!-- Never hydrate (static only) -->
        @defer (hydrate never) {
          <app-static-footer />
        }
      `,
    })
    export class Post {
      postId = input.required<string>();
      chartData = input.required<ChartData>();
    }
    

    Hydration Triggers

    Trigger Description
    hydrate on viewport When element enters viewport
    hydrate on interaction On click, focus, or input
    hydrate on idle When browser is idle
    hydrate on immediate Immediately after load
    hydrate on timer(ms) After specified delay
    hydrate when condition When expression is true
    hydrate never Never hydrate (static)

    Event Replay

    Capture user events before hydration completes:

    import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
    
    export const appConfig: ApplicationConfig = {
      providers: [
        provideClientHydration(withEventReplay()),
      ],
    };
    

    Browser-Only Code

    Platform Detection

    import { PLATFORM_ID, inject } from '@angular/core';
    import { isPlatformBrowser, isPlatformServer } from '@angular/common';
    
    @Component({...})
    export class My {
      private platformId = inject(PLATFORM_ID);
      
      ngOnInit() {
        if (isPlatformBrowser(this.platformId)) {
          // Browser-only code
          window.addEventListener('scroll', this.onScroll);
        }
      }
    }
    

    afterNextRender / afterRender

    Run code only in browser after rendering:

    import { afterNextRender, afterRender } from '@angular/core';
    
    @Component({...})
    export class Chart {
      constructor() {
        // Runs once after first render (browser only)
        afterNextRender(() => {
          this.initChart();
        });
        
        // Runs after every render (browser only)
        afterRender(() => {
          this.updateChart();
        });
      }
      
      private initChart() {
        // Safe to use DOM APIs here
        const canvas = document.getElementById('chart');
        new Chart(canvas, this.config);
      }
    }
    

    Inject Browser APIs Safely

    // tokens.ts
    import { InjectionToken, PLATFORM_ID, inject } from '@angular/core';
    import { isPlatformBrowser } from '@angular/common';
    
    export const WINDOW = new InjectionToken<Window | null>('Window', {
      providedIn: 'root',
      factory: () => {
        const platformId = inject(PLATFORM_ID);
        return isPlatformBrowser(platformId) ? window : null;
      },
    });
    
    export const LOCAL_STORAGE = new InjectionToken<Storage | null>('LocalStorage', {
      providedIn: 'root',
      factory: () => {
        const platformId = inject(PLATFORM_ID);
        return isPlatformBrowser(platformId) ? localStorage : null;
      },
    });
    
    // Usage
    @Injectable({ providedIn: 'root' })
    export class Storage {
      private storage = inject(LOCAL_STORAGE);
      
      get(key: string): string | null {
        return this.storage?.getItem(key) ?? null;
      }
      
      set(key: string, value: string): void {
        this.storage?.setItem(key, value);
      }
    }
    

    Prerendering

    Static Routes

    // app.routes.server.ts
    export const serverRoutes: ServerRoute[] = [
      { path: '', renderMode: RenderMode.Prerender },
      { path: 'about', renderMode: RenderMode.Prerender },
      { path: 'contact', renderMode: RenderMode.Prerender },
      { path: 'blog', renderMode: RenderMode.Prerender },
    ];
    

    Dynamic Routes with getPrerenderParams

    // app.routes.server.ts
    import { RenderMode, ServerRoute, PrerenderFallback } from '@angular/ssr';
    
    export const serverRoutes: ServerRoute[] = [
      {
        path: 'products/:id',
        renderMode: RenderMode.Prerender,
        async getPrerenderParams() {
          // Fetch product IDs to prerender
          const response = await fetch('https://api.example.com/products');
          const products = await response.json();
          return products.map((p: Product) => ({ id: p.id }));
        },
        fallback: PrerenderFallback.Server, // SSR for non-prerendered
      },
      {
        path: 'blog/:slug',
        renderMode: RenderMode.Prerender,
        async getPrerenderParams() {
          const posts = await fetchBlogPosts();
          return posts.map(post => ({ slug: post.slug }));
        },
        fallback: PrerenderFallback.Client, // SPA for non-prerendered
      },
    ];
    

    Prerender Fallback Options

    Fallback Description
    PrerenderFallback.Server SSR for non-prerendered routes
    PrerenderFallback.Client Client-side rendering
    PrerenderFallback.None 404 for non-prerendered routes

    HTTP Caching

    TransferState

    Automatically transfer HTTP responses from server to client:

    import { provideClientHydration, withHttpTransferCacheOptions } from '@angular/platform-browser';
    
    export const appConfig: ApplicationConfig = {
      providers: [
        provideClientHydration(
          withHttpTransferCacheOptions({
            includePostRequests: true,
            includeRequestsWithAuthHeaders: false,
            filter: (req) => !req.url.includes('/api/realtime'),
          })
        ),
      ],
    };
    

    Manual TransferState

    import { TransferState, makeStateKey } from '@angular/core';
    
    const PRODUCTS_KEY = makeStateKey<Product[]>('products');
    
    @Injectable({ providedIn: 'root' })
    export class Product {
      private http = inject(HttpClient);
      private transferState = inject(TransferState);
      private platformId = inject(PLATFORM_ID);
      
      getProducts(): Observable<Product[]> {
        // Check if data was transferred from server
        if (this.transferState.hasKey(PRODUCTS_KEY)) {
          const products = this.transferState.get(PRODUCTS_KEY, []);
          this.transferState.remove(PRODUCTS_KEY);
          return of(products);
        }
        
        return this.http.get<Product[]>('/api/products').pipe(
          tap(products => {
            // Store for transfer on server
            if (isPlatformServer(this.platformId)) {
              this.transferState.set(PRODUCTS_KEY, products);
            }
          })
        );
      }
    }
    

    Build and Deploy

    Build Commands

    # Build with SSR
    ng build
    
    # Output structure
    dist/
    ├── my-app/
    │   ├── browser/      # Client assets
    │   └── server/       # Server bundle
    

    Run SSR Server

    # Development
    npm run serve:ssr:my-app
    
    # Production
    node dist/my-app/server/server.mjs
    

    Deploy to Node.js Host

    // server.ts (generated)
    import { APP_BASE_HREF } from '@angular/common';
    import { CommonEngine } from '@angular/ssr/node';
    import express from 'express';
    import { dirname, join, resolve } from 'node:path';
    import { fileURLToPath } from 'node:url';
    import bootstrap from './src/main.server';
    
    const serverDistFolder = dirname(fileURLToPath(import.meta.url));
    const browserDistFolder = resolve(serverDistFolder, '../browser');
    const indexHtml = join(serverDistFolder, 'index.server.html');
    
    const app = express();
    const commonEngine = new CommonEngine();
    
    app.get('*', express.static(browserDistFolder, { maxAge: '1y', index: false }));
    
    app.get('*', (req, res, next) => {
      commonEngine
        .render({
          bootstrap,
          documentFilePath: indexHtml,
          url: req.originalUrl,
          publicPath: browserDistFolder,
          providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }],
        })
        .then((html) => res.send(html))
        .catch((err) => next(err));
    });
    
    app.listen(4000, () => {
      console.log('Server listening on http://localhost:4000');
    });
    

    For advanced patterns, see references/ssr-patterns.md.

    Repository
    analogjs/angular-skills
    Files