I have an Angular application built with Server-Side Rendering that I am using the prerender command on before deploying the result to Azure SWA.
Within the app, I have a blog post component where the code is as follows:
import { Component } from '@angular/core';
import { BlogPost } from 'src/app/models/blog-post';
import { BlogService } from '../../../services/blog.service';
import { ActivatedRoute, ParamMap, Router, RouterLink } from '@angular/router';
import { take, switchMap } from 'rxjs/operators';
import { Meta } from '@angular/platform-browser';
import { HtmlDirective } from '../../../directives/html.directive';
import { DateDisplayComponent } from '../../shared/date-display/date-display.component';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { NgIf } from '@angular/common';
import { MarkdownModule, provideMarkdown, MarkdownService } from 'ngx-markdown';
@Component({
selector: 'app-blog-post',
templateUrl: './blog-post.component.html',
styleUrl: './blog-post.component.scss',
standalone: true,
imports: [
NgIf,
MatProgressSpinnerModule,
DateDisplayComponent,
HtmlDirective,
MarkdownModule,
RouterLink,
],
providers: [provideMarkdown()],
})
export class BlogPostComponent {
public post: BlogPost;
public isLoading: boolean;
constructor(
private blogService: BlogService,
private route: ActivatedRoute,
private router: Router,
private meta: Meta,
private markdownService: MarkdownService
) {}
ngOnInit() {
this.markdownService.renderer.heading = (text: string, level: number) => {
const escapedText = text.toLowerCase().replace(/[^\w]+/g, '-');
return `<h${level}><a name="${escapedText}" class="anchor" href="#${escapedText}"><span class="header-link"></span></a>${text}</h${level}>`;
};
this.markdownService.renderer.link = (
href: string,
title: string | null | undefined,
text: string
): string => {
return `<a href="${href}" target="_blank">${text}</a>`;
};
this.markdownService.renderer.image = (href: string, title: string, text: string) => {
return `<a href="${href}" target="_blank" title="${text}"><img src="${href}" alt="${text}" /></a>`;
};
this.isLoading = true;
this.route.paramMap
.pipe(
take(1),
switchMap((routeParams: ParamMap) =>
this.blogService.getBlogPost(routeParams.get('postId')!)
)
)
.subscribe({
next: (blogPost: BlogPost) => {
if (!blogPost) {
this.router.navigate(['home']);
}
this.post = blogPost;
this.setMeta(blogPost);
this.isLoading = false;
},
error: (error) => {
console.error(error);
this.isLoading = false;
},
});
}
setMeta(blogPost: BlogPost) {
this.meta.updateTag({ property: 'og:title', content: blogPost.attributes.title });
this.meta.updateTag({
property: 'og:image',
content: '/assets/rarvatar.png',
});
this.meta.updateTag({ property: 'og:url', content: this.router.url });
this.meta.updateTag({
property: 'og:description',
content: blogPost.attributes.description,
});
}
}
The template is defined as follows:
<mat-spinner *ngIf="isLoading"></mat-spinner>
<div class="content-container" *ngIf="!isLoading">
<div class="content-container-card">
<ng-container *ngIf="post">
<h1 class="card-title">{{ post.attributes.title }}</h1>
<app-date-display [dateToDisplay]="post.attributes.publishedAt"></app-date-display>
<p class="blog-post-content" markdown [data]="post.attributes.content"></p>
</ng-container>
</div>
</div>
<div class="back-to-blog-link">
<a [routerLink]="['blog']">Back to Blog</a>
</div>
When this page is rendered, I get the following:
<script id="ng-state" type="application/json">{"2496593754":{"b":[{"id":6,"attributes":{"canonicalUrl":"goodreads-should-not-be-owned-by-amazon","title":"Goodreads Should Not Be Owned By Amazon","description":"I've been dwelling on Goodreads for a bit. I thought I'd write some of those thoughts down.","content":"Goodreads is a good service that should not be owned by Amazon. There are the obvious reasons related to consolidation and things like that, but in this post I'm talking about the quality of the service. I've been using Goodreads for less than a year, and in that time I've felt a few things about it.\n\n## Goodreads is Good\nGoodreads has a lot of neat features! I love the progress tracking by page number. I also like the social features because it's a quick way of saying \"hey book-reading friends! Here's what I'm reading, here's what I read, here's what I want to read\". it alleviates the need for sharing this stuff on larger social media, which is nice. So I like Goodreads.\n\n## Goodreads is Rough\nGoodreads needs some polish. But it's not the \"this is a brand new thing, give it a minute to get things right\" type of polish. It's the \"this has been left alone for a bit too long\" type of polish. While Goodreads is fairly mature at this point, it's also pretty buggy and feels a bit dated in some of its design. The latter isn't a real problem, it could stay that way forever, but the former is something that's only really going to get worse the longer it's left.\n\n## Goodreads has an API...sort of\nGoodreads has an API, but they're not giving tokens out for developers anymore. The last update there was [December 17, 2020](https://help.goodreads.com/s/article/Does-Goodreads-support-the-use-of-APIs), and it read:\n\n> As part of our overall commitment to continually improve our data management, Goodreads no longer issues new developer keys for our public developer API and plans to retire the current version of these tools. While we assess the value of APIs to determine how to support in the future, we continue to support active API users who meet our terms of service. You are welcome to give your feedback on Goodreads APIs by completing the developer API survey.\n\nThat ain't comin' back.\n\n## Goodreads has an Independent Purpose\nPurely as an observation of mine, Goodreads has purpose, but not purpose that benefits at all from being owned by Amazon. Moreover, I'm not sure what benefit Goodreads is to Amazon, besides maybe links to buy books. Did Goodreads not have those before? Also it's nice to be able to share progress directly to Goodreads from my Kindle, but I'm sure this doesn't require being owned by Amazon to be a feature. I think there's passion in what I see built in Goodreads, signs of being built by people who read books. Being acquired by Amazon is obviously in the interest of money, as acquisitions always are, and while it's not lost on me that this acquisition took place over 10 years ago, companies are always reassessing the value of their current assets. As such...\n\n## Goodreads Worries Me\nWith all of the above said, I'm worried about the future of Goodreads. It's a popular platform, but without clear benefit to its owner, and in a time when the work and legacy of many individuals can blink from existence without much visible hesitation, I'm a little bit worried about tracking my reading this way. I hope for a better future for Goodreads, and I think it'd at least be able to be an even better service if they were allowed to be.\n\nIn the meantime, there are several alternatives. The most promising option I saw in my search was [StoryGraph](https://www.thestorygraph.com/). It's built by one person and it looks like a good option. For my uses personally, I need an API to be able to pull my data from and display on my website, but StoryGraph looks to be doing the right things.\n","category":"Thoughts","originallyPostedDate":null,"publishedAt":"2024-02-11T17:13:38.523Z","guid":"09dfcdbe-6ac8-4d7e-a497-56f8bebb1938"}},{"id":5,"attributes":{"canonicalUrl":"home-screen-check-in-february-2024","title":"Home Screen Check-In (February 2024)","description":"Our phone home screens say a lot about us, and I love when people share theirs. Let's check in on the state of my home screen in February 2024.","content":"Some who know me know that I obsess a bit over my Home Screen. I'm pretty picky about the aesthetic, but I also prioritize the accessibility of apps I use frequently and apps I want to use more frequently. I also love when people share their Home Screens, as it gives me new app ideas and an idea of how a person uses their phone. I recently made a few changes, so I thought now would be a good time to share that here.\n\n\n\nThe first 2 screens are the most important. This is where I try to fit everything I use regularly. Arranging my Home Screen definitely helps remind me how many apps I actually use on the regular.\n\n## First Screen\n\n\n\n### Music\n\nThese days, I h
I cut that short for sharing here but basically it's the JSON of my content, not actually prerendering it in HTML. Here's a link to the repo for a better look at the full setup.
All of that to say - the goal here is to have just enough metadata in my page so that when I share my blog posts on social media or apps like Discord, I'll get a proper preview of my post. My understanding is I need server-side rendering for this, alongside properly set up meta tags, but my prerender command doesn't seem to do what I'm expecting. How can I set this up to properly prerender my pages on deploy?
It seems there are a couple issues with your repo.
First: you're not actually pre-rendering your blog content pages, you're pre-rendering your blog overview page however.
By default angular tries to discover all your routes dynamically based on your routing file(s) for pre-rendering, it has discovered your blog overview page, but it can't discover parameterized routes.
You can manually tell angular what routes it should prerender. In the root of your project there is a file
angular.json. Inside this file is a block with the keyprerendercurrently your routes block looks like this:Here you can define extra routes angular should use during the pre-rendering process. You'll want to add all your parameterized routes in here. For example:
More information about pre-rendering and parameterized routes can be found here.
When you add a new blog post you can add it here and run the prerender command again. This flow might not be as 'automated' as you would like. When you have this all working I would advise looking into ISR by RxAngular. It is a combination between server side rendering and pre-rendering.
EDIT: Please note, if the new blog post is not pre-rendered it'll simply get rendered on the server instead, it's not necessary to add the new route to the angular.json routes block and execute the prerender command again. It is nice to have it pre-rendered though (and quicker in terms of loading time).
Second: When loading one of the blog content pages it seems like the blog overview page is served from the sever, and when the browser takes over it'll render the blog content page. You can see this when opening the network tab in your console and looking at the first request. When you look at the preview it shows the blog overview page.
Changing your routes to require a full match should prevent this from happening.
Current
app-routing.module.tsNew