SSR and SSG with Analog
Passionate about frontend tooling, monorepos, and developer experience.
Analog
AnalogJS describes itself as the meta-framework for Angular similar to Next.js for React, Nuxt for Vue, SvelteKit for Svelte or Solidstart for Solid. It is a framework that provides a set of tools and conventions to build Angular applications with server-side rendering (SSR) and static site generation (SSG).
Although SSR is already possible with Angular Universal, Analog aims to make it easier and follow the best practices from the other frameworks.
It builds upon Vite and Nitro, just like Nuxt for example, and has great integrations with Nx and Netlify, providing the option to deploy API routes as serverless functions.
The great thing about Analog is that you can mix and match SSR with SSG and even use some client rendering where it makes sense.
BUT... Analog is not a silver bullet and should not be the default choice for every new Angular project. Simply because Server Side Rendering and Static Site Generation are not always the best choice for every project. A server-side rendered application needs to be hosted on an actual server, which is more expensive than hosting static files like a traditional Single Page Application. So it is important to consider the trade-offs and the requirements of the project before deciding to use Analog. When initial page load time is not a concern and neither SEO is important, imagine a CRM or ERP system, then SSR and SSG are not the best choice.
Getting Started
To get started with Analog, you need Angular and Node installed on your machine. You can then create a new Analog project by running the following command:
npm create analog@latest
Note that Analog ships Nx under the hood, so you get the full power of Nx caching. It may not look like Nx at first, because there is no nx.json or any apps and libs folder. That's because it creates a so-called standalone application.
If you want to leverage the full power of an integrated monorepo with multiple libraries and apps, you can run the following command:
npx create-nx-workspace@latest --preset=@analogjs/platform
Or you can add an Analog project to an existing Nx workspace by running:
npm install @analogjs/platform --save-dev
npx nx g @analogjs/platform:app analog-app
SSR and File-Based Routing
In order to achieve optimal SEO and initial performance, Analog uses server-side rendering, where unlike in a traditional Angular application, the first page load is rendered on the server and then sent to the client. This means that the client receives the HTML of the page already rendered and can start to display it immediately, without waiting for the JavaScript to be downloaded and executed.
The code you write for the server is the same code you write for the client, which makes it easier to reason about and to maintain. But you need to be careful about the context of the code, because on the server you don’t have access to the DOM and the window object, which are available on the client. This may seem odd at first, because we are so used to running code in the browser, but this is just a matter of getting used to it as SSR requires a different mental model.
So, let’s talk about file-based routing next. Forget about loadChildren, loadComponent and most of the Angular router as Analog only uses the Angular router under the hood. But the routing in Analog is much more similar to the routing in Next.js, Nuxt or SvelteKit, where the file path of a page, a special component, determines the route of the page.
For example, if you create a file at src/pages/about.page.ts, then the route of the page will be /about. If you create a file at src/pages/blog/[slug].ts, then the route of the page will be /blog/:slug. This is a great way to organize your pages and to make it easy to understand the structure of your application because the file path correlates directly to the route path in a natural way.
You can also use layout pages that make use of the router-outlet and provide a layout for the child routes. In order to do so, simply create a page component with the same name as a route-fragment, or in other words a directory.
You can also provide some meta-data to a page by creating a const of type RouteMeta inside your page component file.
export const routeMeta: RouteMeta = {
title: 'My Title'
};
Note, that there are many additional things to consider for query params, params, index or redirects but I would simply advice to check out the Analog Docs for this.
SSG and Content Routes
If you want to create a blog for example, you would be interested in using static site generation that takes a simple markdown file and converts it into a static web page. Many other SSG libraries are offering this like Gatsby, 11ty or Astro, but Analog supports SSG out-of-the-box.
In order to render a markdown file, you need to enable content routes by providing provideContent(withMarkdownRenderer()) in your app config.
import { ApplicationConfig } from '@angular/core'; import { provideContent, withMarkdownRenderer } from '@analogjs/content';
export const appConfig: ApplicationConfig = {
providers: [
// ...
provideContent(withMarkdownRenderer()),
],
};
Now, you are ready to create content routes like src/page/content.md which would render whatever is in the content markdown file as html on localhost:4200/content.
Additionally, you can use front-matter to specify meta-data for the markdown file which is not going to be rendered. You might want to specify the title of a blog post, the author or the publish date for example in the file.
---
title: Module Boundaries in Nx
date: 2023-11-06
articleSeries: Modularization
slug: module-boundaries-in-nx
tags: [angular]
description: lorem ipsum
coverImage: https://i.imgur.com/vuqATI0.jpg
---
# Header 1
Lorem ipsum dolor sit amet, consectetur adipiscing elit
It is best to have all your blog posts in one place in the content directory. Then you can load all blog posts at once to display a list of all blog posts on an overview page. Loading all blog posts then is really simple, but requires you to create an interface that corresponds to the front-matter used in the markdown files.
export interface BlogPost {
title: string;
date: Date;
articleSeries: string | null;
description: string;
coverImage: string;
tags: ('angular' | 'react' | 'vue' | 'javascript' | 'nx')[]
}
Then you can use the injectContentFiles function to load all blog posts.
posts = injectContentFiles<BlogPost>();
// returns ContentFile<BlogPost>[]
// now you can access the meta-data defined in the front-matter
// via the attributes property like this:
// this.posts.attributes.title
Opt-In Client Side Rendering
Although SSR and SSG are really powerful in many cases, sometimes you still would prefer to render certain pages only on the client. Such cases would be when you for example want to access the Document or Window object or simply because you have an admin panel that does not need SSR.
Analog does not support any configuration option to enable CSR for certain routes, but you can use this little workaround to disable SSR for some routes.
// main.server.ts
export default async function render(url: string, document: string) {
// client-render any admin URLs
if (url.includes('admin')) {
return Promise.resolve(document);
}
// Server-render anything that's not prerendered
const html = await renderApplication(bootstrap, {
document,
url,
});
return html;
}
Conclusion
Analog is still in its early years of development and is slowly getting more and more traction. I am sure that it will play an important role in the future of the SSR and SSG story for Angular and I believe that it is worth considering that Analog has a major advantage over Next.js, Nuxt and SvelteKit:
It is not backed by a company, but by the community. This means that it is not driven by the needs of a company like Vercel, which offers cloud hosting and computing, such that they would naturally be incentivized to push SSR because it generates more money for them.
By the way, Analog has much more to offer than just SSR and SSG. It has a great story for API routes, for example, and it is a great choice for building fullstack Angular applications. But that’s a story for another time.
"Opinions are my own and not the views of my employer."