How to setup Angular v17 SSR with Docker and DockerCompose
Hello dear fellow NgManiacs! The Angular Framework has gone through massive changes in the past few years. With the new standalone approach since v14, the signals update as well as the new SSR features, angular is becoming a more modern web framework.
Furthermore, in the Ng Conference 2024, Angular Core Team members announced, that features of the Google intern Web Framework Wiz will be merging into Angular’s Open Source Repository. Wiz is used for many complex applications inside Google, like Gmail and Photos.
In Ryan Carniatos (Creator of SolidJs) interview with a Google Wiz Developer **Jatin Ramanathan**, we found a little bit more about Wiz:
> [Wiz] … it’s a pretty old framework, it’s been around for about 10 years now … and I think it’s interesting, because unlike other web frameworks, Wiz actually took SSR extremely seriously right from the start […] the reason for that was because we wanted extremely fast response times. So when you got to a page you should immediately be able to see some content in the first round trip.
>
The whole interview is very interesting and about 3 hours long. You can check it out here: https://www.youtube.com/watch?v=qzOzyUA9kbg.
We can tell from that, that Angular will be even more leaning into SSR features in the future and provide better support for it.
Why switch from CSR (Client Side Rendered) to SSR?
When you build your web application that should be indexed well on Google and other search engines, you are mostly out of luck using CSR. With the CSR approach, the client receives the index.html which uses javascript files to render the page on the client. So to fully see the page’s content, you must run javascript on your browser beforehand.
But indexing doesn’t work that way. Google’s and other indexing bots don’t execute the javascript. What they see is a mostly empty html files without the actual content. What happens is, your page content will not be searchable with the search engines. They are able to display and find your domain, but not what you dynamically published.
The solution to this is Server Side Rendering.
From a developers perspective, it begins with the request from the user to our server, where we host the html rendering machine. In Angulars case, it is a NodeJs server, which bootstraps the Angular application, renders its html state and sends it back to the user. After the page is displayed successfully in the browser, the javascript “rehydrates” and takes control over the DOM. After this happens, the user will be using the standard single page application again.
How do we setup this SSR approach with Docker and Docker Compose?
Anyway, here is code:
import { DOCUMENT, isPlatformBrowser } from '@angular/common';
import {
Component,
LOCALE_ID,
OnInit,
PLATFORM_ID,
Renderer2,
TransferState,
inject,
makeStateKey,
} from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { Title } from '@angular/platform-browser';
import { RouterModule } from '@angular/router';
import { TranslocoService } from '@ngneat/transloco';
import { AuthService } from '@audrey/frontendbay/auth/data-access';
import {
NavigationService,
APP_CONFIG,
} from '@audrey/frontendbay/shared/data-access';
import { TranslocoRootModule } from '@audrey/frontendbay/shared/feature/translate';
import { MaterialModule } from '@audrey/frontendbay/shared/ui/material';
import { take } from 'rxjs';
import { APP_BACKEND_API, APP_BACKEND_INTERNAL_API } from './app.tokens';
import { CookieModule } from 'ngx-cookie';
@Component({
standalone: true,
imports: [RouterModule, MaterialModule, TranslocoRootModule, CookieModule],
selector: 'fbay-root',
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
})
export class AppComponent implements OnInit {
authService = inject(AuthService);
snackBar = inject(MatSnackBar);
navigationService = inject(NavigationService);
translocoService = inject(TranslocoService);
localeId = inject(LOCALE_ID);
platformId = inject(PLATFORM_ID);
renderer2 = inject(Renderer2);
document = inject(DOCUMENT);
appConfig = inject(APP_CONFIG);
title = inject(Title);
transferState = inject(TransferState);
appBackendApi = inject(APP_BACKEND_API, { optional: true });
appBackendInternalApi = inject(APP_BACKEND_INTERNAL_API, { optional: true });
APP_BACKEND_API = makeStateKey('APP_BACKEND_API');
// Init process
ngOnInit(): void {
// Setting the backend api url from the server env variable
if (isPlatformBrowser(this.platformId)) {
this.appConfig.environment.apiUrl = this.transferState.get(
this.APP_BACKEND_API,
this.appConfig.environment.apiUrl
);
} else {
if (this.appBackendApi && this.appBackendInternalApi) {
this.appConfig.environment.apiUrl = this.appBackendInternalApi;
this.transferState.set(this.APP_BACKEND_API, this.appBackendApi);
}
}
if (this.authService.isLoggedIn) {
this.authService.refreshUser$.pipe(take(1)).subscribe();
this.authService.refreshCurrentUser();
}
}
}
1 Comment
Sed nisl nunc, luctus vitae scelerisque ac, euismod fringilla turpis. Fusce consectetur dui a sagittis dapibus. In vel dapibus nisl. Proin nec nibh suscipit, pretium leo sit amet, laoreet risus. Donec tristique rutrum urna in ultrices. 🙂