Complete Guide for Server-Side Rendering (SSR) in Angular
Published: 2024-07-04 16:27:24

This comprehensive post includes a quick introduction to SSR, a detailed setup guide and several best practices with _Angular 17 or even 18_ ([released on May 22nd, 2024](https://blog.angular.dev/angular-v18-is-now-available-e79d5ac0affe)), enhancing the **initial load performance** and thus the **user experience** of modern **Angular applications**. While we do not recommend updating production apps to _V18_ at the time of writing this post, most of the presented SSR features are already stable in _Angular 17_. The new Event Replay feature of _V18_ can easily be added later on. Nevertheless, if you want to use _Material_ and/or _CDK_ with SSR, you might want to upgrade to _V18_ as soon as possible.

In any case, make sure you have already updated to _V17_. If not, follow [my _Angular 17_ upgrade guide](https://www.angulararchitects.io/blog/angular-17-update-control-flow-app-builder-migration/), including the recommended migrations.

The _Angular team_ has recently (well actually for quite some time) been putting in [a huge effort](https://angular.dev/roadmap#fast-by-default) and doing a fantastic job to help us improve the initial load time. SSR plays a significant role in achieving that goal for our framework of choice. Read my post from last July to learn [why initial load performance is so crucial](https://www.angulararchitects.io/blog/why-is-initial-load-performance-so-important/) for your _Angular_ apps.

## Essentials

Let's start with the basics. You can, of course, skip this section if you're already familiar with SSR, and continue with the next section about [building](#build).

### Server-Side Rendering (SSR)

Server-Side Rendering (SSR) is a web development technique where the (in our case node) server generates **the HTML content** of a web page (in our case with JavaScript), providing faster initial load time. This results in a smoother user experience, especially for those on slower networks (e.g. onboard a train in 🇩🇪 or 🇦🇹 – which I happen to be a lot recently 😏) or low-budget devices. Additionally, it improves SEO and crawlability for Social Media and other bots like the infamous ChatGPT.

New _Angular CLI_ projects will automatically prompt SSR (since _Angular 17_):

```bash
ng new your-fancy-app-name
```

For existing projects simply run the `ng add` command (since _Angular 17_):

```bash
ng add @angular/ssr
```

**Warning**: You might have to fix stuff manually (like adding imports of CommonJsDependencies) after adding SSR to your project 😬

Follow the [angular.dev guide](https://angular.dev/guide/ssr#configure-server-side-rendering) for detailed configuration. However, I'd recommend switching to the new Application Builder, which has SSR and SSG baked in. Let's first clarify what SSG does.

### Static Site Generation (SSG)

Static Site Generation (SSG) or Prerendering (like the Angular framework likes to call it), is the technique where HTML pages are prerendered **at build time** and served as static HTML files. Instead of executing on demand, SSG generates the HTML once and serves the same pre-built HTML to all users. This provides even faster load times and further improves the user experience. However, since the HTML is being stored on the server this approach is limited whenever live data is needed.

### Hydration (since NG 16)

Hydration is the process where the prerendered static HTML, generated by SSR or SSG, is enhanced with interactivity on the client side. After the initial HTML is delivered and rendered in the browser, _Angular's JavaScript_ takes over to "hydrate" the static content, attaching **event listeners** and thus making the page fully interactive. This approach combines the fast initial load times of SSR/SSG with the dynamic capabilities of a SPA, again leading to a better overall user experience.

![](./assets/angular-hydration.png)

Before _Angular's Hydration_ feature, the prerendered static DOM would have been destroyed and replaced with the client-side-rendered interactive version, potentially resulting in a layout shift or a full browser window flash aka content flicker – both leading to bad results in performance tools like [**Lighthouse** and **WebPageTest**](https://www.angulararchitects.io/blog/how-to-measure-initial-load-performance/). In my opinion, _Angular SSR_ was not production-ready until supporting Non-Destructive Hydration. This has changed in 2023 since this feature has already become stable in _Angular 17_.

BTW, it's super easy to enable Hydration in _Angular_ 💧

```typescript
export const appConfig: ApplicationConfig = {
	providers: [
		provideClientHydration(), // use NG 16 hydration
	],
};
```

If you're still using _NgModules_ (for reasons), it becomes:

```typescript
@NgModule({
	providers: [provideClientHydration()],
})
export class AppModule {}
```

### Event Replay (in Developer Preview since NG 18)

This example was taken from the official [_Angular blog_](https://blog.angular.dev/event-dispatch-in-angular-89d868d2351c). Consider an app that contains a click button like this:

```html
<button type="button" (click)="onClick()">Click</button>
```

Previously, the event handler `(click)="onClick()"` would only be called once your application has finished Hydration in the client. With Event Replay enabled, **[JSAction](https://github.com/google/jsaction)** is listening at the root element of the app. The library will capture events that (natively) bubble up to the root and replay them once Hydration is complete.

![](./assets/angular-event-replay.png)

If implemented, _Angular_ apps will stop ignoring events before Hydration is complete and allow users to **interact with the page while it's still loading**. There is no need for developers to do anything special beyond enabling this feature.

And again, it's super comfy to enable Event Replay in your app 🤩

```typescript
export const appConfig: ApplicationConfig = {
	providers: [
		provideClientHydration(
			withEventReplay(), // use hydration with NG 18 event replay
		),
	],
};
```

**Note**: At the time of writing this feature is still in Developer Preview, please use it with caution.

## Build

Since _Angular 17_ we have two options for building our _Angular app_.

### Angular's new Application Builder (all-in-one)

As mentioned, I'd recommend switching to the new Application Builder using [**esbuild**](https://esbuild.github.io/) and [**Vite**](https://vitejs.dev/). The advantage of using esbuild over Webpack is that it offers faster build times and more efficient and fine-grained bundling. The significantly smaller bundle also leads to better initial load performance – with or without SSR! Vite is a faster development server supporting extremely fast Hot Module Replacement (HMR).

![](./assets/angular-vite-esbuild.png)

Additionally, both **SSR** and **Prerendering (SSG)** are enabled by default as mentioned in this screenshot from the _Angular Docs_ showing a table of the _Angular Builders_ (note that the `@angular-devkit/build-angular:server` is missing here):

![](./assets/angular-builder.png)

Simply run `ng b` to trigger a `browser` and `server` build in one step. _Angular_ will automatically process the Router configuration(s) to find all unparameterized routes and prerender them for you. If you want, you can add parameterized routes via [a txt file](https://angular.dev/guide/prerendering#prerendering-parameterized-routes). To migrate, read my [automated App Builder migration guide](https://www.angulararchitects.io/blog/angular-17-update-control-flow-app-builder-migration/).

### If still using Webpack (for reasons)

If – for any reason – you're still committed to using **Webpack** to build your web app, you need the browser builder to be configured in your `angular.json` (might be in `project.json` if you're using Nx). This will, of course, be added automatically once you run `ng add @angular/ssr`.

```json
{
	"server": {
		"builder": "@angular-devkit/build-angular:server",
		"options": {
			"outputPath": "dist/your-fancy-app-name/server",
			"main": "server.ts",
			"tsConfig": "tsconfig.server.json"
		}
	}
}
```

Note: The referenced `server.ts` lies in the project's root and is the entry point of your server application. With this dedicated server builder, there is also a dedicated `tsconfig.server.json` (whereas the new Application Builder recommended previously merges the two tsconfig files for more convenience) 🤓

Now let's quickly have a look at the build scripts:

**Important note**: If you haven't started using `pnpm`, you're missing out. However, of course, both `npm run ...` and `yarn ...` will also work instead of `pnpm ...`.

```bash
pnpm dev:ssr
ng run your-fancy-app-name:serve-ssr
```

Similar to `ng s`, which offers live reload during development, but uses server-side rendering. Altogether, it's a bit slower than `ng s` and won't be used a lot apart from quickly testing SSR on `localhost`.

```bash
pnpm build:ssr
ng build && ng run your-fancy-app-name:server
```

Builds both the `browser` application and the `server` script in production mode into the `dist` folder. Use this command when you want to build the project for deployment or run performance tests. For the latter, you could use [serve](https://www.npmjs.com/package/serve) or a similar tool to serve the application on your `localhost`.

## Deploy

You have two options for deployment. While both are technically possible, I'd recommend using the second one.

### Using on-demand rendering mode via node server

Starts the server for serving the application with node using SSR.

```bash
pnpm serve:ssr
node dist/your-fancy-app-name/server/main.js
```

I've shown a detailed [example Docker container here](https://www.angulararchitects.io/blog/how-to-use-angular-ssr-with-hydration/).

**Caution**: _Angular_ requires a certain _Node.js_ version to run, for details see the [Angular version compatibility matrix](https://angular.dev/reference/versions#actively-supported-versions).

### Using build time SSR with SSG (recommended)

This option doesn't need a node environment on the server and is also way faster than the other one.

```bash
pnpm prerender
ng run your-fancy-app-name:prerender
```

Used to generate an application's prerendered routes. The static HTML files of the prerendered routes will be attached to the `browser` build, not the `server`. Now you can deploy your `browser` build to whatever host you want (e.g. [**nginx**](https://nginx.org/en/)). You're doing the same thing as without SSR with some extra directories (and `index.html` files).

**Important note**: If you're using the new (and recommended) Application Builder, you can skip these steps for building and prerendering since they're already included in `ng b`. In other words, you have zero extra work for building including SSR & SSG – pretty great, huh? 😎

## Debug

The first step in debugging is looking for misconfigurations in your `angular.json` (`project.json`) or some errors in your `server.ts`. If both look good, there is no definite way to debug SSR and SSG issues. Feel free to [**contact me**](mailto:alexander.thalhammer@angulararchitects.io) if you're experiencing any troubles.

### How to avoid the most common issue

Browser-specific objects like **document**, **window**, **localStorage**, etc., do **NOT** exist on the `server` app. Since these objects are not available in a Node.js environment, trying to access them results in errors. This can be avoided by using the document injector or by running code explicitly in the browser:

```typescript
import { Component, inject, PLATFORM_ID } from "@angular/core";
import { DOCUMENT, isPlatformBrowser, isPlatformServer } from "@angular/common";

export class AppComponent {
	private readonly platform = inject(PLATFORM_ID);
	private readonly document = inject(DOCUMENT);

	constructor() {
		if (isPlatformBrowser(this.platform)) {
			console.warn("browser");
			// Safe to use document, window, localStorage, etc. :-)
			console.log(document);
		}

		if (isPlatformServer(this.platform)) {
			console.warn("server");
			// Not smart to use document here, however, we can inject it ;-)
			console.log(this.document);
		}
	}
}
```

### Browser-Exclusive Render Hooks

An alternative to injecting `isPlatformBrowser` are the two render hooks `afterNextRender` and `afterRender`, which can only be used within the [**injection context**](https://angular.dev/guide/di/dependency-injection-context) (basically field initializers or the constructor of a component):

The `afterNextRender` hook, takes a callback function that runs **once** after the **next** change detection – a bit similar to the init lifecycle hooks. It's used for performing one-time initializations, such as integrating 3party libs or utilizing browser APIs:

```typescript
export class MyBrowserComponent {
	constructor() {
		afterNextRender(() => {
			console.log("hello my friend!");
		});
	}
}
```

If you want to use this outside of the injection context, you'll have to add the injector:

```typescript
export class MyBrowserComponent {
	private readonly injector = inject(Injector);

	onClick(): void {
		afterNextRender(
			() => {
				console.log("you've just clicked!");
			},
			{ injector: this.injector },
		);
	}
}
```

The `afterRender` hook, instead, is executed after **every upcoming** change detection. So use it with extra **caution** – same as you would do with the `ngDoCheck` and `ng[Content|View]Checked` hooks because we know that Change Detection will be triggered a lot in our _Angular_ app – at least until we go **zoneless**, but that story that will be presented in yet another blog post 😎

```typescript
export class MyBrowserComponent {
	constructor() {
		afterRender(() => {
			console.log("cd just finished work!");
		});
	}
}
```

If you'd like to deep dive into these hooks, I recommend reading this [blog post](https://netbasal.com/exploring-angulars-afterrender-and-afternextrender-hooks-7133612a0287) by Netanel Basal.

### Angular Hydration in DevTools

The awesome _Angular_ collaborator [Matthieu Riegler](https://x.com/Jean__Meche) has recently added **hydration debugging** support to the _Angular's DevTools_! Which are, besides all Chromium derivatives, also available for Firefox, but then why would somebody still use that Boomer browser? 😏

![](./assets/angular-SSR-hydration-in-dev-tools.png)

Note the 💧 for hydrated components. Even though this feature was announced in the _Angular 18_ update, it also works in past versions.

### Other SSR Debugging Best Practices

Here is a collection of some more opinionated debugging recommendations:

- **DevTools**: Besides the updated _Angular DevTools_ tab, inspect your HTML with the **Elements** tab and your API requests with the **Network** tab. BTW, you should also simulate a slow connection here when performance testing your app.
- **Console**: I personally like to log everything into my **Console**. Not interested in a logger lib since I'm fine with console.log() and maybe some other levels. Any console logs will be printed into the terminal where `ng b` or `pnpm dev:ssr` or `pnpm serve:ssr` has been run. We don't need to talk about logging into the browser's console on production, or do we?
- **Node.js**: Start your SSR server with the --inspect flag to get more information: `node --inspect dist/server/main.js`
- **Fetching**: Ensure all necessary data is available at render time. Use _[Angular's TransferState](https://angular.dev/api/core/TransferState?tab=description)_ to transfer data from the server to the client.
- **Routing**: Make sure all routes are correctly configured and match on both the `browser` and `server` builds.
- **Environments**: Ensure environment variables are correctly set up for both `browser` and `server` builds.
- **3rd-party Libs**: As always, be very careful about what you include in your project. Some libraries might not be implemented correctly and thus not work in an SSR context. Use conditional imports or platform checks to handle these cases or, even better, get rid of those libs in the first place.

That's all I have got so far. If you've got anything to add, feel super free to [**contact me**](mailto:alexander.thalhammer@angulararchitects.io)!

## Advanced

### Disable Hydration for Components

Some components may not work properly with hydration enabled due to some issues, like DOM Manipulation. As a workaround, you can add the `ngSkipHydration` attribute to a component's tag to skip hydrating the entire component.

```html
<app-example ngSkipHydration />
```

Alternatively, you can set `ngSkipHydration` as a host binding.

```typescript
@Component({
	host: { ngSkipHydration: "true" },
})
class DryComponent {}
```

Please use this carefully and thoughtfully. It is intended as a last-resort workaround. Components that have to skip hydration should be considered bugs that need to be fixed.

### Use Fetch API instead of XHR

The **[Fetch API](https://web.dev/articles/introduction-to-fetch)** offers a modern, promise-based approach to making HTTP requests, providing a cleaner and more readable syntax compared to the well-aged `XMLHttpRequest`. Additionally, it provides better error handling and more powerful features such as support for streaming responses and configurable request options. It's also recommended to be used with SSR by the _[Angular team](https://stackoverflow.com/questions/77512654/angular-detected-that-httpclient-is-not-configured-to-use-fetch-apis-angul/77512684#77512684)_.

To enable it, simply add `withFetch()` to your `provideHttpClient()`:

```typescript
export const appConfig: ApplicationConfig = {
	providers: [provideHttpClient(withFetch())],
};
```

If you're still using _NgModules_ (for reasons), this becomes:

```typescript
@NgModule({
	providers: [provideHttpClient(withFetch())],
})
export class AppModule {}
```

### Configure SSR API Request Cache

The _Angular HttpClient_ will cache all outgoing network requests when running on the server. The responses are serialized and transferred to the browser as part of the server-side HTML. In the browser, _HttpClient_ checks whether it has data in the cache and if so, reuses that instead of making a new HTTP request during the initial load. _HttpClient_ stops using the cache once an application becomes stable in the browser.

By default, HttpClient caches all `HEAD` and `GET` requests that don't contain **Authorization** or **Proxy-Authorization** headers. You can override those settings by using `withHttpTransferCacheOptions` when providing hydration:

```typescript
export const appConfig: ApplicationConfig = {
	providers: [
		provideClientHydration(
			withEventReplay(),
			withHttpTransferCacheOptions({
				filter: (req: HttpRequest<unknown>) => true, // to filter
				includeHeaders: [], // to include headers
				includePostRequests: true, // to include POST
				includeRequestsWithAuthHeaders: false, // to include with auth
			}),
		),
	],
};
```

### Use Hydration support in Material 18 and CDK 18 💧

Starting with _Angular Material 18_, all components and primitives are fully SSR and Hydration compatible. For information, read this [blog post](https://blog.angular.dev/material-3-experimental-support-in-angular-17-2-8e681dde650e). On how to upgrade your _Angular Material_ app, consult the docs on [migrate from Material 2 to Material 3](https://material.angular.io/guide/material-2-theming#how-to-migrate-an-app-from-material-2-to-material-3).

### Combine SSR for static & CSR for user content 🤯

The future is here! With _**Angular 17 Deferrable Views**_ you can easily mix SSR/SSG with CSR 🎉

The usage is pretty straightforward: Currently, all `@defer` components will render their `@placeholder` on the server and the real content will be loaded and rendered once they have been triggered (by _on_ or _when_) in the browser. Learn more about [how to use and trigger Deferrable Views](https://www.angulararchitects.io/blog/how-to-improve-initial-load-performance-with-angular-17s-deferrable-views/).

Here are some primitive **examples** of how to combine SSR and CSR:

- Static pages: Use SSR
- Static content with live updates: Use deferred components for the live content and SSR for the rest
- Product list with prices depending on the user: Defer price components and use SSR for the rest
- List with items depending on the user: Defer the list component and use SSR for the rest

So basically, everywhere you need CSR (e.g. for user-dependent content), you need to `@defer` those parts. Use the `@placeholder` (and `@loading`) to show spinners or equivalents to inform the user that something is still being loaded. Also, make sure to reserve the right amount of space for the deferred components – avoid layout shifts at all costs!

### SEO and Social Media Crawling 🔍

If you want to look good on Google and/or social media platforms, make sure to implement all the necessary **meta tags** in SSR. For a comprehensive list, including some tools and tips, [jump here](https://moz.com/blog/meta-data-templates-123).

```typescript
export class SeoComponent {
	private readonly title = inject(Title);
	private readonly meta = inject(Meta);

	constructor() {
		// set SEO metadata
		this.title.setTitle("My fancy page/route title. Ideal length 60-70 chars");
		this.meta.addTag({ name: "description", content: "My fancy meta description. Ideal length 120-150 characters." });
	}
}
```

### Use SSR & SSG within AnalogJS 🚀

[AnalogJS](https://analogjs.org/) is _the_ meta-framework built on top of _Angular_ – like [Next.js](https://nextjs.org/) (React), [Nuxt](https://nuxt.com/) (VueJS), [SolidStart](https://start.solidjs.com/) (Solid). Analog supports SSR during development and building for production. If you want to know more, read the announcement of [version 1.0](https://dev.to/analogjs/announcing-analogjs-10-19an) by [Brandon Roberts](https://x.com/brandontroberts) or wait for my **upcoming blog post** 😏

### Angular SSR & SSG featuring I18n

Since the _Angular I18n_ only works during built-time, it's fairly limited. Therefore, we recommend using [Transloco](https://jsverse.github.io/transloco/) (or [NGX-Translate](https://github.com/ngx-translate/core)). When adding Transloco by running `ng add @jsverse/transloco`, you'll be prompted for SSR usage. However, you can also manually add the necessary changes for SSR (see [Transloco Docs](https://jsverse.github.io/transloco/docs/ssr-support)):

```typescript
@Injectable({ providedIn: "root" })
export class TranslocoHttpLoader implements TranslocoLoader {
	private readonly http = inject(HttpClient);

	getTranslation(lang: string) {
		return this.http.get<Translation>(`${environment.baseUrl}/assets/i18n/${lang}.json`);
	}
}
```

```typescript
export const environment = {
	production: false,
	baseUrl: "http://localhost:4200", // <== provide base URL for each env
};
```

This will SSR everything in the default language and then switch to the user's language (if different) in the browser. While this generally works, **it's definitely not ideal to see the text being swapped**. Furthermore, we need to ensure there are **no layout shifts** upon switching! If you come up with any ideas on how to improve this, please [**contact me**](mailto:alexander.thalhammer@angulararchitects.io)!

### Caution with Module / Native Federation

At the time of writing this post, the **Angular Architects'** federation packages do not support SSR:

- [**Module Federation**](https://github.com/angular-architects/module-federation-plugin/blob/main/libs/mf/README.md) using custom Webpack configurations under the hood and
- [**Native Federation**](https://github.com/angular-architects/module-federation-plugin/blob/main/libs/native-federation/README.md) same API &ndash; but using browser-native [Import Maps](https://www.angulararchitects.io/blog/import-maps-the-next-evolution-step-for-micro-frontends-article/) and thus also working with `esbuild`

You **won't be able to use SSR** out of the box when you set up a federated Angular app. While there are plans to support that, we currently cannot provide a date when this will be possible.

For the time being _the master of module federation **Manfred Steyer**_ introduced an interesting approach, combining SSR with **native federation**. If the microfrontends are integrated via the _Angular Router_, then a server-side and a client-side variant can be offered per **routes** definition:

```typescript
function isServer(): boolean {
	return isPlatformServer(inject(PLATFORM_ID));
}

function isBrowser(): boolean {
	return isPlatformBrowser(inject(PLATFORM_ID));
}

const appRoutes = [
	{
		path: "flights",
		canMatch: [isBrowser],
		loadChildren: () => loadRemoteModule("mfe1", "./Module").then((m) => m.FlightsModule),
	},
	{
		matcher: startsWith("flights"),
		canMatch: [isServer],
		component: SsrProxyComponent,
		data: {
			remote: "mfe1",
			url: "flights-search",
			tag: "app-flights-search",
		} as SsrProxyOptions,
	},
];
```

Learn more about this approach in [this article on devm.io](https://devm.io/angular/microfrontend-module-federation-ssr-hydration) or check out the `ssr-islands` branch of [Manfred's example on GitHub](https://github.com/manfredsteyer/module-federation-plugin-example/) to see an implemented example. While this setup reduces conflicts by isolating microfrontends, it introduces complexity in maintaining separate infrastructure code for both the client and server sides, making it challenging. Therefore, it's crucial to assess if this **trade-off** suits your specific project needs and meets your **architecture and performance goals**.

### Caution with PWA

Be careful if you are using _Angular SSR_ in combination with the _Angular PWA_ service worker because the behavior deviates from default SSR. The initial request will be server-side rendered as expected. However, subsequent requests are handled by the service worker and thus client-side rendered.

Most of the time that's what you want. Nevertheless, if you want a fresh request you can use the `freshness` option as _Angular PWA_ `navigationRequestStrategy`. This approach will try a network request and fall back to the cached version of `index.html` when offline. For more information, consult the _[Angular Docs](https://angular.dev/ecosystem/service-workers/config#navigationrequeststrategy)_ and read this [response on Stack Overflow](https://stackoverflow.com/questions/56383569/how-to-make-angular-universal-and-pwa-work-together/56400078#56400078).

## Workshops

If you want to deep dive into Angular, we offer a variety of workshops – both in English and German.

- [**Performance Workshop**](https://www.angulararchitects.io/en/training/angular-performance-optimization-workshop/) 🚀
- [**Accessibility Workshop**](https://www.angulararchitects.io/en/training/angular-accessibility-workshop/) ♿
- [**Styling Workshop**](https://www.angulararchitects.io/en/training/angular-styling-workshop/) 🎨

## Outlook

### Partial Hydration (NG 19 or 20)

Partial hydration, announced at [ng-conf](https://ng-conf.org/) and [Google I/O](https://io.google/2024/explore/7deddebc-3cae-4285-b2a9-affb5296102e/) 2024, is a technique that allows incremental hydration of an app after server-side rendering, improving performance by loading less JavaScript upfront.

```html
@defer (render on server; on viewport) {
<app-deferred-hydration />
}
```

The prototype block above will render the calendar component on the server. Once it reaches the client, _Angular_ will download the corresponding JavaScript and hydrate the calendar, making it interactive only after it enters the viewport. This is in contrast to full hydration, where all the components on the page are rendered at once.

It builds upon `@defer`, enabling _Angular_ to render main content on the server and hydrate deferred blocks on the client after being triggered. The _Angular team_ is actively prototyping this feature, with an early access program available for Devs building performance-critical applications.

## Conclusion

In summary, implementing Server-Side Rendering (SSR) in _Angular_, along with Static Site Generation (SSG), Hydration and Event Replay, significantly improves the initial load performance of your _Angular_ apps. This leads to a better user experience, especially on slower networks or low-budget devices, and enhances SEO and crawlability of your web app.

By following the steps and best practices outlined in this guide, you can achieve better load performance for your apps with minimal effort. The new Application Builder makes building and deploying very smooth.

Feel free to [**contact me**](mailto:alexander.thalhammer@angulararchitects.io) for further questions or join our [**Performance Workshop 🚀**](https://www.angulararchitects.io/en/training/angular-performance-optimization-workshop/) to learn more about performance optimization for _Angular_ apps.

## References

- [Why is Initial Load Performance so Important?](https://www.angulararchitects.io/blog/why-is-initial-load-performance-so-important/) by Alexander Thalhammer
- [Angular 16 - official blog post](https://blog.angular.dev/angular-v16-is-here-4d7a28ec680d) by Minko Gechev
- [Angular Update Guide to V17 incl. migrations](https://www.angulararchitects.io/blog/angular-17-update-control-flow-app-builder-migration/) by Alexander Thalhammer
- [Angular 17’s Deferrable Views](https://www.angulararchitects.io/blog/how-to-improve-initial-load-performance-with-angular-17s-deferrable-views/) by Alexander Thalhammer
- [Angular 18 - official blog post](https://blog.angular.dev/angular-v18-is-now-available-e79d5ac0affe) by Minko Gechev
- [Angular’s after(Next)Render hooks](https://netbasal.com/exploring-angulars-afterrender-and-afternextrender-hooks-7133612a0287) by Netanel Basal
- [Angular Event Replay blog post](https://blog.angular.dev/event-dispatch-in-angular-89d868d2351c) by Jatin Ramanathan, Tom Wilkinson
- [Angular SSR Docker example](https://www.angulararchitects.io/blog/how-to-use-angular-ssr-with-hydration/) by Alexander Thalhammer

This blog post was written by [Alex Thalhammer](https://alex.thalhammer.name/). Follow me on [GitHub](https://github.com/L-X-T), [X](https://twitter.com/LX_T) or [LinkedIn](https://at.linkedin.com/in/thalhammer).