How to Build a Blog with Analog and Angular ✍️
Tags: webdev, angular, metaframework
Published: 2023-08-21 13:10:00

Putting your thoughts together in your personal blog is a great way to build a further understanding about a topic, and also helps others who come across it in the future. Building a blog is also one of the best ways you can learn a web development stack. Meta-frameworks of today make this process easier. This post shows you how to build a static blog with Angular using Analog.

Analog is the meta-framework that helps you ship applications and websites faster with Angular.

{% youtube H4U6udLcM-Q %}

## Getting Started 🎽

To create a new Analog project, you can use the `create-analog` package with your package manager of choice:

```sh
npm create analog@latest my-analog-blog
```

Follow the prompts to choose Analog, the latest version of Angular, and Tailwind:

```sh
✔ Select a template: › Analog
✔ Select a variant: › angular-v16
✔ Would you like to add Tailwind to your project? … no
```

And go into the newly created folder

```sh
cd my-analog-blog
```
Next, install the dependencies:

```sh
npm install
```

To start the development server, use the start command:

```sh
npm start
```

Navigate to http://localhost:5173 to see the application running in your browser.

## Adding Markdown Support 🔌

Analog comes with support for using markdown as content out of the box. To add support for rendering markdown content, add the `provideContent(withMarkdownRenderer())` from the `@analogjs/content` package to the `providers` in the `app.config.ts`

```ts
import { provideHttpClient } from '@angular/common/http';
import { ApplicationConfig } from '@angular/core';
import { provideClientHydration } from '@angular/platform-browser';
import { provideFileRouter } from '@analogjs/router';
import { provideContent, withMarkdownRenderer } from '@analogjs/content';

export const appConfig: ApplicationConfig = {
  providers: [
    provideFileRouter(),
    provideHttpClient(),
    provideClientHydration(),
    provideContent(withMarkdownRenderer())
  ],
};
```

This allows you to use markdown content directly as routes, and to render markdown content inside of Angular components.

## Creating Content Routes with Markdown 🧠

Some pages in your blog are purely content-focused, where you  only need to display content from a markdown file. Markdown files can be use directly as pages.

Create a markdown file in the `src/app/pages` directory named `about.md`:

```markdown
---
title: About Me
---

This page is about me
```

> Frontmatter is supported also for additional metadata, such as title, description, metatags, and more.

Navigate to `http://localhost:5173/about` to see the about page.

## Creating and Displaying a Blog Post ✍️

Markdown files can also be used in combination with route components to display content. To create a custom page for a blog post, Analog provides a built-in component.

First, create a markdown file in `src/content/my-first-post.md` for the post. 

```markdown
---
title: My First Post
slug: my-first-post
---

Hello World
```

Next, define an interface for the shape of the data returned from the frontmatter. For this example, the interface is placed in the `src/app/models/post.ts` file.

```ts
export interface BlogPost {
  title: string;
}
```

Next, create a page component for the blog. This page serves as the parent layout for the listing the blog posts and displaying an individual blog post.

Create the page component file in `src/app/pages/blog.page.ts`.

```ts
import { Component } from '@angular/core'; 
import { RouterOutlet } from '@angular/router';

@Component({
  standalone: true,
  imports: [RouterOutlet],
  template: `
    <h1>My Blog</h1>

    <router-outlet />
  `,
})
export default class BlogPage {} 
```

Next, create the page component file for displaying a blog post. 

Define a page component in `src/app/pages/blog/[slug].page.ts`

```ts
import { MarkdownComponent, injectContent } from '@analogjs/content';
import { AsyncPipe, NgIf } from '@angular/common';
import { Component } from '@angular/core'; 

import { BlogPost } from 'src/app/models/post';

@Component({
  standalone: true,
  imports: [MarkdownComponent, NgIf, AsyncPipe],
  template: `
    <div *ngIf="post$ | async as post">
      <h2>{{ post.attributes.title }}</h2>

      <analog-markdown [content]="post.content" />
    </div>
  `,
})
export default class BlogPostPage {
  post$ = injectContent<BlogPost>();
} 
```

By default, Analog reads the `slug` route parameter to determine which content file to read from the `src/content` folder.

The `injectContent` reads the content from the markdown file based on the `slug` route parameter. The content and attributes are provided as an Observable that could be converted to a signal also. The `MarkdownComponent` displays the rendered markdown content.

This page creates the `/blog/:slug` route for displaying blog posts. Save the changes and visit the `http://localhost:5173/blog/my-first-post` to view the blog post.

> Read more about [how routing works](https://analogjs.org/docs/features/routing/overview).

## Listing Blog Posts 📜

Next up, let's display the blog posts. For this page, an index page for at `http://localhost:5173/blog` will display a list of the blog posts.

Create a page component under `src/app/pages/blog/index.page.ts` to define the `/blog` route.

```ts
import { Component } from '@angular/core'; 
import { AsyncPipe, NgFor } from '@angular/common';
import { injectContentFiles } from '@analogjs/content';
import { RouterLink } from '@angular/router';

import { BlogPost } from 'src/app/models/post';

@Component({
  standalone: true,
  imports: [NgFor, RouterLink, AsyncPipe],
  template: `
    <h2>Posts</h2>

    <ul>
      <li *ngFor="let post of posts">
        <a [routerLink]="['/blog', post.slug]">{{ post.attributes.title }}</a>
      </li>
    </ul> 
  `,
})
export default class IndexPage {
  posts = injectContentFiles<BlogPost>();
} 
```

The `injectContentFiles()` function provides a static list of all the markdown files inside the `src/content` folder, with the frontmatter already parsed and available to use in the component. 

The `BlogPost` interface is used to add type safety for using the post `attributes` in the template.

Visit the `http://localhost:5173/blog` to display the blog posts and navigate to the blog post created earlier.

## Redirecting to Blog List 🔄

A list of blog posts can now be displayed, as well as individual blog posts. A redirect can also be used to navigate from the home page `/` to the `/blog` page.

> A page component is not necessary to define a redirect. 

Replace the contents of the `src/app/pages/index.page.ts` with the redirect:

```ts
import { RouteMeta } from "@analogjs/router";

export const routeMeta: RouteMeta = {
  redirectTo: '/blog',
  pathMatch: 'full',
};
```

RouteMeta allows you to attach additional route metadata to a route. Here it's using a redirect, but other metadata such as guards, resolvers, page titles, and more can be added.

> Read more about [route metadata](https://analogjs.org/docs/features/routing/metadata).

## Pre-Rendering Static Pages 📃

Lastly, for a blog, using Static Site Generation by pre-rendering the content allows for better SEO, and enables the site to be navigated without JavaScript turned on.

To pre-render routes, in the `vite.config.ts`, update the `analog()` plugin with a config object that includes a `prerender` property. Define the routes to be pre-rendered into static HTML files during build time.

```ts
  analog({
    prerender: {
      routes: [
        '/',
        '/blog',
        '/blog/my-first-post',
        '/about'
      ],
    }
  })
```

Routes can also be defined using an async function if you need to define them dynamically.

> Support for generating [sitemaps](https://analogjs.org/docs/features/server/static-site-generation#sitemap-generation) and [an RSS feed](https://analogjs.org/docs/features/api/overview#defining-xml-content) is also included.

## Deployment 🚀

To build the blog for deployment, run the build:

```sh
ng build
```

By default, the static assets are placed into the `dist/analog/public` folder for deployment. You can deploy Analog sites/applications to many different providers with little to no configuration including Netlify, Vercel, Firebase, and more.

Visit the [deployment docs](https://analogjs.org/docs/features/deployment/overview) to learn more.

And that's it! 🤩

The GitHub repo for this example blog is at: https://github.com/brandonroberts/my-analog-blog

Now the really creative part begins where you bring your own styles and customizations to the blog to really make it unique. 😉

## Example Projects

If you're looking for some inspiration on building your own blog, check out some blogs built with Analog and Angular.

Robin Goetz - [Blog](https://goetzrobin.github.io/) [GitHub](https://github.com/goetzrobin/goetzrobin.github.io)
Matthieu Riegler aka. Jean Mèche - [Blog](https://riegler.fr) [GitHub](https://github.com/JeanMeche/riegler.fr)
Luis Castro - [Blog](https://mrrobot.dev) [GitHub](https://github.com/luishcastroc/blog)
Chris Perko - [Blog](https://perko.dev) [GitHub](https://github.com/BaronVonPerko/perko-dev)
Preston Lamb - [Blog](https://www.prestonlamb.com/)

## Partner with Analog 🤝

We are looking for companies to partner with on the Analog project to support development of the project. Thanks to [Snyder Technologies](https://snyder.tech/) for being an early [adopter and promoter](https://twitter.com/benpsnyder/status/1671389794545328128?s=20) of Analog.

{% tweet 1671389794545328128 %}

Find out more information on our [partnership opportunities](https://analogjs.org/docs/sponsoring) or reach out directly to sponsor[at]analogjs.org. 

## Join the Community 🥇

- Visit and Star the [GitHub Repo](https://github.com/analogjs/analog)
- Join the [Discord](https://chat.analogjs.org)
- Follow us on [Twitter](https://twitter.com/analogjs)

If you enjoyed this post, click the :heart: so other people will see it. Follow [me on Twitter](https://twitter.com/brandontroberts) and subscribe to my [YouTube Channel](https://youtube.com/brandonrobertsdev?sub_confirmation=1) for more content!