Server-Side Rendering

Back

Loading concept...

🚀 Angular Server-Side Rendering: The Magic Kitchen

Imagine you run a restaurant. Normally, when a customer orders food, you give them raw ingredients and tell them to cook it themselves at their table (that’s like a regular Angular app—the browser does all the work). But what if you had a magical kitchen that prepares the food BEFORE the customer even sits down? That’s Server-Side Rendering (SSR)!


🍳 What is Server-Side Rendering?

When someone visits your Angular website, instead of sending an empty page and making their browser build everything, the server cooks up the complete page first and sends it ready-to-eat.

Without SSR:

User → Empty Page → Browser Downloads JS → Browser Builds Page → User Sees Content

With SSR:

User → Server Prepares Full Page → User Sees Content Instantly!

Why Does This Matter?

  1. Speed: Users see content immediately
  2. Google loves it: Search engines can read your content
  3. Works everywhere: Even on slow phones or bad internet

📦 The Angular SSR Package

Angular gives you a special toolbox called @angular/ssr. Think of it like buying a pre-made kitchen kit instead of building one from scratch.

Installing Your Kitchen Kit

ng add @angular/ssr

This single command does THREE things:

  1. Installs @angular/ssr package
  2. Creates a server file for you
  3. Updates your project settings

What You Get

graph TD A["ng add @angular/ssr"] --> B["📦 SSR Package"] A --> C["🖥️ Server Entry File"] A --> D["⚙️ Config Updates"] B --> E["Ready to Render!"] C --> E D --> E

⚙️ SSR Configuration

After installing, Angular creates a special file called server.ts. This is your kitchen’s control panel.

The Server File Structure

// server.ts - Your Kitchen Control Panel
import { AppServerModule } from './src/main.server';
import { renderApplication } from '@angular/platform-server';

export function bootstrap() {
  return renderApplication(AppServerModule, {
    document: '<app-root></app-root>'
  });
}

Angular.json Updates

Your angular.json gets a new “server” section:

{
  "projects": {
    "my-app": {
      "architect": {
        "server": {
          "builder": "@angular-devkit/build-angular:server",
          "options": {
            "outputPath": "dist/my-app/server"
          }
        }
      }
    }
  }
}

Simple explanation: This tells Angular WHERE to put the server-ready version of your app.


🔄 Transfer State: The Memory Box

Here’s a problem: Your server fetches data (like a list of products). Then your browser wakes up and fetches THE SAME data again. That’s wasteful!

Transfer State is like a memory box the server packs for the browser.

graph TD A["Server Fetches Data"] --> B["📦 Pack in Transfer State"] B --> C["Send Page + Box to Browser"] C --> D["Browser Opens Box"] D --> E["No Need to Fetch Again!"]

How to Use It

import { TransferState, makeStateKey } from '@angular/core';

// Create a key (like a label on the box)
const PRODUCTS_KEY = makeStateKey<Product[]>('products');

@Injectable()
export class ProductService {
  constructor(
    private http: HttpClient,
    private transferState: TransferState
  ) {}

  getProducts() {
    // Check if browser already has the data
    const stored = this.transferState.get(PRODUCTS_KEY, null);

    if (stored) {
      return of(stored); // Use packed data!
    }

    // Otherwise, fetch and pack for next time
    return this.http.get<Product[]>('/api/products').pipe(
      tap(data => this.transferState.set(PRODUCTS_KEY, data))
    );
  }
}

Think of it like: Mom packs your lunch. You don’t need to buy lunch at school!


🔍 Platform Detection: Where Am I Running?

Your code needs to know: “Am I running on the server or in the browser?”

Why? Because some things only work in browsers:

  • window object
  • document object
  • localStorage
  • Browser APIs

The Detection Tools

import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { PLATFORM_ID, inject } from '@angular/core';

@Component({...})
export class MyComponent {
  private platformId = inject(PLATFORM_ID);

  ngOnInit() {
    if (isPlatformBrowser(this.platformId)) {
      // ✅ Safe to use browser stuff
      console.log(window.innerWidth);
      localStorage.setItem('visited', 'true');
    }

    if (isPlatformServer(this.platformId)) {
      // 🖥️ Running on server
      console.log('Preparing page on server...');
    }
  }
}

Quick Reference

Platform window exists? document exists? Use for
Browser ✅ Yes ✅ Yes User interactions
Server ❌ No ⚠️ Limited Pre-rendering

💧 Hydration Setup: Waking Up the Page

When the server sends a pre-built page, it’s like a sleeping robot. It looks complete, but nothing works yet—buttons don’t click, forms don’t submit.

Hydration is the process of waking up this robot, making it interactive.

graph TD A["Server Sends HTML"] --> B["User Sees Page Instantly"] B --> C["JavaScript Loads"] C --> D["💧 Hydration Starts"] D --> E["Angular Attaches to HTML"] E --> F["Page is NOW Interactive!"]

Enabling Hydration

In your app.config.ts:

import { provideClientHydration } from '@angular/platform-browser';

export const appConfig = {
  providers: [
    provideClientHydration()
  ]
};

That’s it! One line enables the magic.

What Hydration Does

  1. Angular loads in the browser
  2. It finds the server-rendered HTML
  3. Instead of destroying and rebuilding, it connects to existing HTML
  4. Adds event listeners to make things interactive

Without hydration: Page flickers as Angular rebuilds everything With hydration: Smooth transition, no flicker!


🎛️ Hydration Options: Fine-Tuning the Wake-Up

Angular gives you options to customize how hydration works.

Option 1: Skip Hydration for Specific Components

Some components don’t need to be interactive (like a static header):

import { ngSkipHydration } from '@angular/platform-browser';

@Component({
  selector: 'app-static-header',
  template: `<header>Welcome!</header>`,
  host: { 'ngSkipHydration': 'true' }
})
export class StaticHeaderComponent {}

Or in the template:

<app-static-header ngSkipHydration></app-static-header>

Option 2: Enable HTTP Cache Transfer

Transfer API responses automatically:

provideClientHydration(
  withHttpTransferCacheOptions({
    includeHeaders: ['X-Custom-Header']
  })
)

Option 3: No HTTP Cache

If you want fresh data every time:

provideClientHydration(
  withNoHttpTransferCache()
)

Options Summary

graph TD A["Hydration Options"] --> B["Skip Specific Components"] A --> C["Transfer HTTP Cache"] A --> D["Disable HTTP Cache"] B --> E["Faster Load for Static Parts"] C --> F["Avoid Duplicate Requests"] D --> G["Always Fresh Data"]

🎮 Event Replay: Never Miss a Click!

Here’s a tricky problem: The user sees the page and clicks a button, but JavaScript hasn’t loaded yet. What happens to that click?

Without Event Replay: Click is lost forever 😢 With Event Replay: Click is recorded and replayed when ready! 🎉

How It Works

graph TD A["User Clicks Button"] --> B{JS Loaded?} B -->|No| C["📼 Record the Click"] B -->|Yes| D["Handle Click Normally"] C --> E["JS Finishes Loading"] E --> F["⏯️ Replay All Recorded Events"] F --> D

Enabling Event Replay

import { provideClientHydration, withEventReplay } from '@angular/platform-browser';

export const appConfig = {
  providers: [
    provideClientHydration(
      withEventReplay()  // Enable event replay!
    )
  ]
};

What Gets Replayed?

  • ✅ Click events
  • ✅ Focus events
  • ✅ Blur events
  • ✅ Input events
  • ✅ Form submissions

Real Example: User rapidly clicks “Add to Cart” 3 times before JS loads. With event replay, all 3 clicks are captured and processed!


🎁 Putting It All Together

Here’s your complete SSR setup:

Step 1: Install

ng add @angular/ssr

Step 2: Configure (app.config.ts)

import { ApplicationConfig } from '@angular/core';
import {
  provideClientHydration,
  withEventReplay
} from '@angular/platform-browser';

export const appConfig: ApplicationConfig = {
  providers: [
    provideClientHydration(
      withEventReplay()
    )
  ]
};

Step 3: Use Transfer State (Optional but Recommended)

import { TransferState, makeStateKey } from '@angular/core';

const DATA_KEY = makeStateKey<any>('myData');

// In your service...
if (this.transferState.hasKey(DATA_KEY)) {
  return this.transferState.get(DATA_KEY, null);
}

Step 4: Protect Browser-Only Code

if (isPlatformBrowser(this.platformId)) {
  // Browser-only code here
}

🌟 The Complete Picture

graph TD A["🚀 User Visits Site"] --> B["🖥️ Server Renders Page"] B --> C["📦 Pack Transfer State"] C --> D["📤 Send Complete HTML"] D --> E["👀 User Sees Content"] E --> F["📥 JavaScript Loads"] F --> G{🎮 User Clicked?} G -->|Yes| H["📼 Events Recorded"] G -->|No| I["Continue Loading"] H --> I I --> J["💧 Hydration Starts"] J --> K["🔗 Connect to Existing HTML"] K --> L["📦 Unpack Transfer State"] L --> M["⏯️ Replay Recorded Events"] M --> N["✨ Fully Interactive!"]

💡 Key Takeaways

Concept One-Line Summary
SSR Server prepares the page before sending
@angular/ssr The package that makes it all work
Configuration server.ts + angular.json updates
Transfer State Memory box to avoid duplicate fetches
Platform Detection Know if you’re on server or browser
Hydration Wake up the static page
Hydration Options Customize the wake-up process
Event Replay Never lose user clicks

🎯 You Did It!

You now understand how Angular SSR works from start to finish. Your server is the magical kitchen that prepares delicious, ready-to-eat pages. The memory box (Transfer State) prevents waste. Platform detection keeps you safe. And hydration with event replay ensures nothing is ever lost.

Go build something amazing! 🚀

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.