Angular 18 SSR Starter

Introducing the first dynamic page server side rendering starter for Angular featuring Agility CMS. This utilizes SSR capabilities of Angular 18 to pre-render a users sitemap into pages served statically for fast page delivery and SEO optimization.

This build features a streamlined architecture and easy to understand development patterns common to Angular developers that will naturally fit within the modern language of the Agility ecosystem.

The repository can be found at https://github.com/agility/agilitycms-angular-starter Currently this version is located on the angular-ssr branch

Pre-requesites

  • Node v20+

Installation

  • Close this Repository
  • npm install to install the Node Modules

Environmental Variables

You will need to get the following from the Agility Manager;

GUID API_PREVIEW_KEY API_FETCH_KEY LOCALE SITEMAP

https://manager.agilitycms.com/settings/apikeys

You will need to populate an .env file in the root of your project as follows.

This will use dotenv.config.json to write your environment.ts and environment.prod.ts files required by Angular. This approach avoids exposing your API keys to your Git repository.

AGILITY_GUID=e13c7b01-u
AGILITY_PREVIEW=true
AGILITY_API_PREVIEW_KEY=AngularPreview.6617c54b87588d941d32416a9dfb1e8fd9e556439984e8236ac75896e47ae02a
AGILITY_API_FETCH_KEY=AngularFetch.a20b40fd8cd25e02ba62ca5c3acbaae5512c1d633b51ea104ac28f9bc3b9d44d
AGILITY_LOCALE=en-us
AGILITY_SITEMAP=website

Build Commands

  • npm run dev start the development server
  • npm run build pre-renders the routes via agility-routes.txt then builds the production server
  • npm run start starts the production server
  • npm run prerender:routes pre-renders only the agility-routes.txt

Why not 'ng run commands?

Although Angular's ng commands are still available, we’ve introduced a number of steps in the building and serving of the Angular application. As such its advisable to use the npm run commands above. However, if you still desire to use the ng commands please be sure to run these prior to trying to run your application to generate the necessary environment and routes files.

  • npm run envinit
  • npm run prerender:routes

Key Features

  • Dynamic SSR - Supports server-side rendering for dynamic pages generated from an Agility CMS.
  • Pre-rendering and TransferState - Improves initial page load performance and reduces redundant requests.
  • Hybrid SSR - Uses a blend of SSR and client-side routing for optimal load times and user experience.
  • Seamless Agility CMS Integration - Designed to fit naturally with Agility’s headless CMS ecosystem, making it easier to manage content dynamically.
  • Seamless Agility CMS Integration - Designed to fit naturally with Agility’s headless CMS ecosystem, making it easier to manage content dynamically.

Preview Mode

This feature works as a client side only feature to ensure changes are loaded directly from the API instead of using the pre-rendered page and content data.

Limitations of Angular 18 SSR when handling Dynamic Pages

Angular is built primarily as a client-side framework, designed to load an initial shell of the application in the browser and handle navigation on the client side. With this architecture, Angular’s built in router dynamically updates the content without reloading the page.

Angular can handle SSR for the initial page load, however, once the app is hydrated, navigation between routes is managed by the client-side router, which means http requests are visible and no longer Server Side. The Angular router doesn’t natively support dynamic changes to the base route or URL content without a full page reload.

Angular’s SSR can be thought of as a “Hybrid” SSR feature as it does not function the same as a more fully developed client router like in React or Vue.

This starter implements a TransferState functionality to reduce subsequent page requests from the client however if you want to completely eliminate client side requests you will need to serve the production build.

The Angular TransferState API is designed to persist data between the server-rendered page and the client, but it only applies to the initial load, not for subsequent navigation events. After the first load, if you navigate to another route, TransferState will not automatically fetch fresh data from the server.

Angular 18’s SSR functionality is definitely a step in the right direction for Angular however it’s clear there’s still room for improvement on connecting the the hydration flow from server to client when navigating via the Angular router.

Other Workarounds - Pre-render all page data and content lists

Solution Architecture

This does not include the boilerplate created by Angular however this is the basic overview of the starter architecture of what we’ve added to power Agility CMS.

Angular SSR Data Pre-loading Branch

This is a solution to using the Angular Router to avoid client side requests.

The way it achieves this is to pre-load the website data by running a pre-fetch process in Node when building the site.

This solution is comprised of 3 main files, one of which is already present in the base branch angular-ssr

  • prerender-routes.ts - generates an agility-routes.txt file for Angular to prerender the page content
  • prerender-pages.ts - generates a JSON file of all your Agility Pages, effectively a Sync mechanism
  • prerender-content.ts - generates a JSON file of all the content lists you require

Added Build Commands

Two new build commands are introduced with this branch

  • npm run prerender:pages
  • npm run prerender:content

Component Retrofitting

In order to pull data client side components have to be updated to first check the local JSON files and then set that as the initial TransferState.

import agilityPagesData from '../data/pages.json';

 async preloadTransferStateData() {
    let pagePath = this.location.path().split('?')[0] || '/home';
    if (pagePath === '') pagePath = '/home';
    for (const key in agilityPagesData) {
      const pageKey = makeStateKey<any>(key);
      this.transferState.set(pageKey, (agilityPagesData as any)[key]);
    } 
  }

Development Caution

This has not been tested on websites with a large volume of pages or content lists. Anything pre-rendered will in-fact add to the build times of the application. As well, since the Angular Router is held within memory, all pre-loaded data is also held within memory adding to the application overhead.