We use cookies to enhance your experience on the site
CodeWorlds

The blade's road to the battlefield - SSR and Deployment

Your application is ready in the dojo, @name - but a true samurai does not stay in the training hall. This lesson is the blade's road to battle: first you decide how each page is forged (rendering), then you forge the finished blade (build), and finally you send it out into the world (deployment). We will walk that road one gate at a time.

Gate one: where the page is forged (SSR)

By default Angular assembles the page only once it reaches the visitor's browser - that is like handing him raw steel and telling him to forge the sword himself. On a slow device the visitor stares at an empty board for a long time, and search engines see nothing but emptiness. Server-Side Rendering (SSR) moves the forging to the server: it renders the HTML on the server and sends the ready markup to the browser, so the visitor gets a finished page straight away.

1# Add SSR to an existing project
2ng add @angular/ssr
3
4# Or right away when creating a new one
5ng new my-app --ssr

A single

ng add @angular/ssr
brings in everything you need - the server, the configuration and the entry points. Note the exact shape of that command: SSR is not a schematic you reach for with
ng generate
, it is not a package you pull in with
npm install
, and it is not a flag you bolt onto
ng build
. One
ng add
and Angular prepares the dojo for you, so you never write the server by hand.

Once it is installed, the heart of SSR is a single provider that tells Angular to render on the server as well:

1// app.config.server.ts
2import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
3import { provideServerRendering } from '@angular/platform-server';
4import { appConfig } from './app.config';
5
6const serverConfig: ApplicationConfig = {
7  providers: [
8    provideServerRendering()
9  ]
10};
11
12export const config = mergeApplicationConfig(appConfig, serverConfig);

Look closely at

mergeApplicationConfig
- we are not building a separate application for the server, we are adding the server settings on top of the shared configuration from
app.config.ts
. Thanks to that, the very same application runs in the browser and on the server, and you have no second codebase to maintain.

There is one trap waiting at this gate. Code that runs on the server has no browser around it - objects such as

localStorage
or
window
simply do not exist there, and reaching for them breaks the render. Storing state in
localStorage
is browser storage, by the way, not a rendering strategy - it has nothing to do with where the HTML is produced. To tell the two worlds apart Angular gives you the
PLATFORM_ID
token together with the
isPlatformBrowser()
and
isPlatformServer()
helpers from
@angular/common
:

1// storage.service.ts
2import { Injectable, inject, PLATFORM_ID } from '@angular/core';
3import { isPlatformBrowser } from '@angular/common';
4
5@Injectable({ providedIn: 'root' })
6export class StorageService {
7  private platformId = inject(PLATFORM_ID);
8
9  setItem(key: string, value: string): void {
10    if (isPlatformBrowser(this.platformId)) {
11      localStorage.setItem(key, value);
12    }
13  }
14}

isPlatformBrowser(this.platformId)
answers one question: am I running in the browser right now? Only inside that guard do you touch
localStorage
. On the server the check returns false, the branch is skipped, and the render survives untouched. Every browser-only API in an SSR application belongs behind such a guard.

Gate two: forge in advance what never changes (SSG)

SSR forges the page on every request. But an "About" page or a samurai profile looks the same for everyone - why hammer it out again and again? Prerendering (SSG) generates static HTML pages at application build time, once, and stores each of them as a ready file.

1// angular.json
2{
3  "architect": {
4    "prerender": {
5      "builder": "@angular/build:prerender",
6      "options": {
7        "routes": [
8          "/",
9          "/about",
10          "/samurai/1",
11          "/samurai/2"
12        ]
13      }
14    }
15  }
16}

In the

routes
field you list the paths that should be forged ahead of time. This is the fastest page there is - the server computes nothing, it simply hands over a finished file. Do not confuse prerendering with its neighbours: it does not render a page only after the visitor clicks something, it does not remove unused code from the bundle, and it does not compress images - those belong to other stages of the build. The rule for choosing is simple: pages that look the same for everyone go to prerendering, while pages that depend on the logged-in visitor stay with SSR.

Armor for the times without a network: PWA

A true warrior fights on even when the supply lines fail. A PWA (Progressive Web App) is the armor that keeps your application working without an internet connection - the browser remembers its assets and serves them from its own store.

1ng add @angular/pwa

Just as with SSR, one command straps on the whole suit of armor:

ng
, then
add
, then
@angular/pwa
, in that order and nothing more. The heart of a PWA is the service worker - a sentry that intercepts requests and hands over the remembered assets whenever the network is gone:

1// Service worker registration
2import { isDevMode } from '@angular/core';
3import { provideServiceWorker } from '@angular/service-worker';
4
5export const appConfig: ApplicationConfig = {
6  providers: [
7    provideServiceWorker('ngsw-worker.js', {
8      enabled: !isDevMode(),
9      registrationStrategy: 'registerWhenStable:30000'
10    })
11  ]
12};

The crucial line is

enabled: !isDevMode()
- the sentry stands guard only in the production build. That is deliberate: while you train in the dojo you want to see every change the instant you make it, and a service worker that remembers assets would keep handing you stale versions and lead you astray for hours.

Gate three: forge the finished blade (build)

The road always runs in the same order. First you write the application code, and only then do you forge it into its battle version -

ng build
gathers all of that code, strips out the parts nobody uses and optimizes the size of what is left.

1# Production build with optimizations
2ng build --configuration=production
3
4# Build with a report of what weighs the most
5ng build --stats-json
6npx webpack-bundle-analyzer dist/browser/stats.json
7
8# Preview the finished application locally
9npx http-server dist/browser

The first command generates the files in the

dist/
folder - for a browser application, in
dist/browser
. Removing unused code from the bundle happens right here, during the build; that is tree-shaking, and it is a different thing from prerendering. The second command is the habit of a seasoned warrior:
webpack-bundle-analyzer
draws a map showing which library weighs the most - before the visitor feels it as slow loading, you already know what to trim. The third one lets you inspect the result locally, exactly as the world will see it.

The final gate: send the blade into the world (deployment)

The blade is forged - time for the battlefield. Deployment means sending the files generated in the

dist/
folder to a platform that will serve them to the world. Each of the popular platforms does this in one or two commands:

1# Vercel
2npm i -g vercel
3vercel
4
5# Netlify
6npm i -g netlify-cli
7netlify deploy --prod
8
9# Firebase
10ng add @angular/fire
11ng deploy

All three work the same way: you install the platform's tool, and a single command ships the finished build. Choosing between them is mostly a question of where you already keep an account - what matters is that an application with SSR needs a platform that runs Node (Vercel, Netlify), while purely prerendered pages are content with any file hosting.

Remember the road from this lesson rather than the commands: you write the application code, you decide how the page comes into being (SSR for content that depends on the visitor, SSG for content that is the same for all), then you forge it with

ng build --configuration=production
, which generates the files in the
dist/
folder, and finally one command deploys it to a platform such as Vercel or Netlify. It is the same road for every Angular application.

Go to CodeWorlds