Notes on building a blog with Scully and Angular Material feature image

Thoughts on building a blog with Scully and Angular Material

I recently decided to build this blog to keep all my notes, ideas, and reviews that I accumulate during learning. Of course, this left me thinking for a while, trying to pick the right tech.

Initially, I was tempted to build a full-stack app with a back-end, database, etc. I decided that that would be too much of an effort for a simple blog, and it wouldn't be much of a learning experience as I built similar projects in the past.

I considered using WordPress as a headless CMS and building a frontend with React, Angular, or even Inferno but decided against it as well. Ultimately, I tried to find the best tool for the job rather than building something crazy and convoluted.

I write documentation in markdown files in my day job, so I thought it would be nice to do the same with my posts. At the end of the day, why do I need to mess around with setting up databases, paying for hosting, potentially having to configure servers when I can create a file, push it to my GitHub repo, and have it rendered on my site.

At this point, you might have guessed that I went with a static site generator. I looked mainly into Hugo, Gatsby, and Scully. I also considered trying to 'marry' Inferno to Next for SSG, and I might come back to this idea later. I quite liked Hugo, and I even set up a simple website (using premade theme) for my wife with it, but I didn't have the time to learn its templating language and all the extra stuff that goes into building themes from scratch. I didn't look into Vue-based SSGs as I'm most familiar with Angular and React and didn't want to spend more time than necessary. I know Vue isn't that difficult, and I used it at work, but I guess I'm just not used to it yet.

Out of all of the frameworks, I'm most familiar with Angular, so I chose Scully. Using Scully is enjoyable and pretty easy once you have your app built in Angular. As usual, I'm not going to write a tutorial on setting up Scully or building a blog as these topics are well covered online. Instead, I'll go over a couple of issues I encountered.

You can check out this blog for information on how to set up Scully.

The only thing I have to point out is that the code provided here didn't work for me, so I had to modify it.

The author provides a way to grab post metadata stored in the front-matter to use it on an individual post page:

// blog-post.component.ts
export class BlogPostComponent {
  constructor(
    private activatedRoute: ActivatedRoute,
    private scully: ScullyRoutesService
  ) {}

  $blogPostMetadata = combineLatest([
    this.activatedRoute.params.pipe(pluck('postId')),
    this.scully.available$
  ]).pipe(
    map(([postId, routes]) =>
      routes.find(route => route.route === `/blog/${postId}`)
    )
  );

and the markup:

<!-- blog-post.component.html -->
<h1 *ngIf="$blogPostMetadata | async as blogPost">
  Blog Post by {{blogPost.authorName}}
</h1>
<hr />
<!-- This is where Scully will inject the static HTML -->
<scully-content></scully-content>
<hr />
<h2>End of content</h2>

I couldn't get the async pipe to work as is, so I changed it up a bit:

<div class="content-wrapper container-xl">
  <img
    class="post-image mb-2"
    src="{{ ($blogPostMetadata | async)?.imageUrl }}"
    alt="{{ ($blogPostMetadata | async)?.title }} feature image"
  />
  <scully-content></scully-content>
  <a mat-button color="accent" [routerLink]="['/blog']">BACK TO BLOG</a>
</div>

The next issue I faced was to do with PrismJS not highlighting the code. There is a guide on how to set it up available on the Scully.io website. As I understood, the issue was caused by the highlight method running before prismjs was made available within the component. The method also fired only once and wouldn't work in other blog posts. I solved it by using ngDoCheck combined with setTimeout:

  ngDoCheck() {
    if (!this.isHighlighted) {
      setTimeout(() => {
        this.highlightService.highlightAll();
        this.isHighlighted = true;
      }, 800);
    }
  }

  ngOnDestroy() {
    this.isHighlighted = false;
  }

isHighlighted is set to false by default. Within ngDoCheck, highlightAll() method is triggered with 800 ms delay, then isHighlighted is set to true and reset to false when the component is destroyed.

Another thing I couldn't find an obvious solution to is rendering static routes with Scully. You are expected to provide a dynamic route like /blog/:postID for Scully to render the content. But what do you do when you want to render, say, an 'About' page from a markdown file? Well, there isn't a simple answer to this question as /about won't work, and there isn't any information in the official Scully docs.

After digging through some old Gitter posts, I found an old plugin, which, as far I understand, was written by one of Scully devs. It did the trick for me though I saw people complaining about not having the front-matter in the file. I didn't check that as I didn't need it on my About page.

If you have the same use case and are struggling with finding a solution, create a new file in scully/plugins folder and paste this code into it:

import { scullyConfig } from '@scullyio/scully';
import { join } from 'path';

const { registerPlugin } = require('@scullyio/scully');

registerPlugin('router', 'staticRoute', async (route, config) => {
  return [
    {
      route,
      templateFile: join(scullyConfig.homeFolder, config.file),
      postRenderers: ['contentFolder'],
    },
  ];
});

import it to Scully config file and configure your route

export const config: ScullyConfig = {
  // ...
  routes: {
    // ...
    '/about': {
      type: 'staticRoute',
      file: 'ia-blog-app/pages/about/about.md',
    },
  },
};

I won't bore you with details on how to deploy it to Netlify, as it's pretty straightforward and there are several guides available. Here's one. I don't think you need to build your Angular app every single time, though. My workflow is a bit different because I write markdown files directly on my laptop and push it to my repo (instead of using a CMS). I figured if I write files and update the repo myself anyway, why don't I create a publish command that will also run netlify deploy --prod (you do need a CLI for that).

And the last issue I faced was related to Angular Material theming. There are a few articles on how to customize Angular Material themes are they are clear for the most part. But none of them mentions how to customize the background. To keep the long story short, you can use the $mat-dark-theme-background SCSS mixin for that. In my case, I created a partial called _palettes where I store all my custom colors and palettes. It looks something like that:

//...
$ia-warn: (
  50: #ffe9eb,
  100: #ffc8cd,
  200: #ffa3ab,
  300: #ff7e89,
  400: #ff6370,
  500: #ff4757,
  600: #ff404f,
  700: #ff3746,
  800: #ff2f3c,
  900: #ff202c,
  A100: #ffffff,
  A200: #ffffff,
  A400: #ffcfd1,
  A700: #ffb6b9,
  contrast: (
    50: $dark-secondary-text,
    100: $dark-secondary-text,
    200: $dark-secondary-text,
    300: $dark-primary-text,
    400: $dark-primary-text,
    500: $dark-primary-text,
    600: $dark-primary-text,
    700: $dark-primary-text,
    800: $dark-primary-text,
    900: $dark-primary-text,
    A100: $dark-secondary-text,
    A200: $dark-secondary-text,
    A400: $dark-secondary-text,
    A700: $dark-secondary-text,
  ),
);

$mat-dark-theme-background: (
  status-bar: $dark-primary-text,
  app-bar: map-get($ia-orange, 500),
  background: $darker-bg,
  hover: rgba($off-white, 0.04),
  card: $lighter-bg,
  dialog: $lighter-bg,
  disabled-button: rgba($off-white, 0.12),
  raised-button: $lighter-bg,
  focused-button: $light-focused,
  selected-button: map-get($mat-grey, 900),
  selected-disabled-button: $lighter-bg,
  disabled-button-toggle: $dark-primary-text,
  unselected-chip: map-get($mat-grey, 700),
  disabled-list-option: $dark-primary-text,
  tooltip: map-get($mat-grey, 700),
);

// Foreground palette for dark themes.
$mat-dark-theme-foreground: (
  base: $off-white,
  divider: $light-dividers,
  dividers: $light-dividers,
  disabled: $light-disabled-text,
  disabled-button: rgba($off-white, 0.3),
  disabled-text: $light-disabled-text,
  elevation: black,
  hint-text: $light-disabled-text,
  secondary-text: $light-secondary-text,
  icon: $off-white,
  icons: $off-white,
  text: $off-white,
  slider-min: $off-white,
  slider-off: rgba($off-white, 0.3),
  slider-off-active: rgba($off-white, 0.3),
);

There are several decent articles on angular material theming online:

https://material.angular.io/guide/theming
https://www.positronx.io/create-angular-material-8-custom-theme/
https://indepth.dev/posts/1320/custom-theme-for-angular-material-components-series-part-1-create-a-theme
https://medium.com/@tomastrajan/the-complete-guide-to-angular-material-themes-4d165a9d24d1 (this one requires medium membership)

I hope you'll find these notes helpful and if you are an Angular developer and need a static site, definitely try out Scully.

BACK TO BLOG