Telerik blogs

Learn how to build a blog using Angular and Analog. This tutorial will guide you through the process of setting up a Markdown-powered blog using Analog.

Ever wanted to build a blog with Angular, but wished it was as easy as writing Markdown and hitting publish? I’ll show you how to do that in under 30 minutes. This tutorial will walk you through building a Markdown-powered blog using Analog—a meta-framework for Angular that brings static site generation, content routing and all the modern goodies you’ll love.

We’re going to cover the following topics:

  • Creating blog posts with Markdown
  • Displaying posts and building routes
  • Static page prerendering (Hybrid-SSR)
  • Generating a sitemap

Let’s get started!

Project Setup

We’ll start with creating a new Analog project. You’ll need Node.js and npm (or pnpm/yarn) installed.

Open the terminal and run the command npx create-analog@latest my-analog-blog to create the project. You’ll get a prompt to choose how you’d like the project to be scaffolded. Choose the “Blog” option since that’s what we want to build.

You’ll be asked to choose a syntax highlighter for the blog. Let’s go with shiki for this tutorial.

The next prompt will ask if you want to use single file components (SFC). Select no (N) to use the traditional Angular component structure.

The next prompt will ask if you want to use Tailwind CSS. This is optional, but it’s a great choice for styling your blog quickly. If you choose yes (Y), Analog will set it up for you.

After all that, the project should be created with a Git repository initialized. Run the following commands to install the dependencies and start developing your blog:

You may have the CLI add "marked": "^7.0.0" to your package.json file, but @analog/content has a higher version specified as its peer dependency. Change the version of marked to ^15.0.0 in your package.json before you run npm install.

cd my-analog-blog
npm install

The project is now ready for us to add/publish content and style it to our liking. We will walk through the code that makes this work in the following sections, but first let’s add a post and see how it looks.

Add a Markdown file named hello-world.md to the src/content/blog/ folder. Then paste in the following content:

---
title: "Hello World: Using Analog & Angular"
slug: "hello-world"
description: "My first Analog blog post!"
coverImage: "https://images.unsplash.com/photo-1493612276216-ee3925520721?auto=format&fit=crop&w=464&q=80"
---

Here is a sample of some basic Markdown syntax that can be used when writing Markdown content in Astro.

## Headings

The following HTML `<h1>``<h6>` elements represent six levels of section headings. `<h1>` is the highest section level while `<h6>` is the lowest.

# H1

## H2

### H3

#### H4

##### H5

###### H6

## Paragraph

Xerum, quo qui aut unt expliquam qui dolut labo. Aque venitatiusda cum, voluptionse latur sitiae dolessi aut parist aut dollo enim qui voluptate ma dolestendit peritin re plis aut quas inctum laceat est volestemque commosa as cus endigna tectur, offic to cor sequas etum rerum idem sintibus eiur? Quianimin porecus evelectur, cum que nis nust voloribus ratem aut omnimi, sitatur? Quiatem. Nam, omnis sum am facea corem alique molestrunt et eos evelece arcillit ut aut eos eos nus, sin conecerem erum fuga. Ri oditatquam, ad quibus unda veliamenimin cusam et facea ipsamus es exerum sitate dolores editium rerore eost, temped molorro ratiae volorro te reribus dolorer sperchicium faceata tiustia prat.

Itatur? Quiatae cullecum rem ent aut odis in re eossequodi nonsequ idebis ne sapicia is sinveli squiatum, core et que aut hariosam ex eat.

## Blockquotes

The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a `footer` or `cite` element, and optionally with in-line changes such as annotations and abbreviations.

Let’s see how the blog is rendered.

Open the terminal and run the npm run dev command. This starts your Angular app. Open http://localhost:5173/ and you should see the blogs listed.

Blog Demo

Working with Markdown Content

It feels like magic, but you now have a functional Markdown blog. The scaffolded project already had the necessary files and dependencies. But how does it work? Let’s break it down.

Rendering Markdown and Content Routes

Analog uses the @analogjs/content package to handle Markdown content. It provides a powerful API for loading and rendering Markdown files. We used the provideContent() function, along with the withMarkdownRenderer() and withShikiHighlighter() in the src/app/app.config.ts file. The withMarkdownRenderer() function is responsible for rendering the Markdown content, while the withShikiHighlighter() function is responsible for highlighting the code blocks using Shiki. Besides Shiki, it has built-in support for PrismJS, which can be enabled using the withPrismHighlighter() function passed to provideContent().

All these make it possible to render the Markdown content in the src/content folder as you saw earlier. It also makes it possible to have a Markdown page in the src/app/pages folder. For example, if you create a file called src/app/pages/about.md, it will be rendered as a page at /about. Let’s try that.

Add a file called about.md in the src/app/pages folder with the following content:

---
title: About Me
meta:
  - name: description
    content: This is the about me page
  - property: og:title
    content: About Page
---

## About Analog

I'm a software engineer with a passion for building web applications. I love using Angular and Analog to create fast, modern websites.

[Back Home](/)

The meta field is optional. It allows you to add meta tags for SEO purposes. The title field is used as the page title.

Save that and visit /about in the browser to see your new page.

Markdown Content Collection

Analog gives you a flexible way to collect and store markdown files in your project. Instead of having individual markdown files in the pages directory, you can put them in the src/content directory. This is useful for blogs, documentation or any other content that needs to be organized. These Markdown files support front matter for metadata. You’ve seen this already when you added Markdown files in the previous section.

Blog List Page

The pages/blog/index.page.ts file is responsible for rendering the list of blog posts. It uses the injectContentFiles() function to fetch the list of Markdown files in the src/content/ directory. We get the lists and inject it into the Page component as follows:

export default class BlogComponent {
  readonly posts = injectContentFiles<PostAttributes>();
}

The PostAttributes interface defines the structure of the front matter for each post, and you can add more fields as needed.

The component’s template then renders a list of posts using that data:

<h1>Blog Archive</h1>

@for (post of posts; track post.attributes.slug) {
<a [routerLink]="['/blog/', post.attributes.slug]">
  <h2 class="post__title">{{ post.attributes.title }}</h2>
  <p class="post__desc">{{ post.attributes.description }}</p>
</a>
}

This code uses the @for directive to loop through each post and display its title and description. The routerLink directive creates a link to the individual blog post page, where slug is used as the post’s URL slug.

Blog Post Page

Analog provides a MarkdownComponent and injectContent() function for rendering Markdown content with front matter.

The pages/blog/[slug].page.ts file is responsible for rendering the individual blog post. It uses the injectContent() function to fetch the content of the Markdown file based on the slug in the URL. The component’s template uses the MarkdownComponent to render the content as you can see below:

@Component({
  selector: "app-blog-post",
  standalone: true,
  imports: [AsyncPipe, MarkdownComponent],
  template: `
    @if (post$ | async; as post) {
      <article>
        <img class="post__image" [src]="post.attributes.coverImage" />
        <analog-markdown [content]="post.content" />
      </article>
    }
  `,
  styles: `
    .post__image {
      max-height: 40vh;
    }
  `,
})
export default class BlogPostComponent {
  readonly post$ = injectContent<PostAttributes>("slug");
}

You may have noticed that rendering Markdown with the MarkdownComponent is as simple as <analog-markdown [content]="post.content" />. It applies the necessary HTML and uses Shiki to highlight code blocks.

You still have the flexibility to customize the CSS styles for the rendered Markdown content.

Prerendering Static Pages

Analog supports server-side rendering, static site generation (SSG) and a hybrid approach. By hybrid, I mean a mix of server-rendered dynamic pages and prerendered static pages. By default, Analog apps are configured as SSR, but we’re going to configure the blog routes to be prerendered static pages. This is awesome for performance and SEO, while still allowing you to have dynamic pages for other parts of your site.

When you open vite.config.ts, you’ll see the Analog plugin config:

export default defineConfig(({ mode }) => ({
  // collapsed code block
  plugins: [
    analog({
      content: {
        highlighter: "shiki",
      },
      prerender: {
        routes: ["/blog", "/blog/2022-12-27-my-first-post"],
      },
    }),
  ],
  // collapsed code block
}));

The /blog and /blog/2022-12-27-my-first-post routes are prerendered. What we want is to prerender all the blog posts from the content directory. We’re going to need an object that loads the list of content and return the urls to pre-render.

Let’s add an object to the routes config to prerender the contents from the content directory. Update the routes config to the following:

prerender: {
  routes: async () => [
    "/blog",
    {
      contentDir: "src/content/blog",
      transform: (file: PrerenderContentFile) => {
        // use the slug from frontmatter if defined, otherwise use the file's basename
        const slug = file.attributes["slug"] || file.name;
        return `/blog/${slug}`;
      },
    },
  ],
},

The transform function maps the file paths to the URLs. The returned string should be the URL path in your app. The contentDir value is the path to the content directory, but it can also be a glob pattern.

Autogenerate a Sitemap

Sitemaps help search engines discover your content. Analog can generate a sitemap automatically when you enable the sitemap option in the prerender config. To make your blog discoverable by search engines, enable the sitemap option:

prerender: {
  // the existing routes config
  sitemap: {
    host: 'https://pmbanugo.me/',
  },
},

After building the site, you’ll find the sitemap in the dist/analog/public directory.

That’s a Wrap!

You now have a Markdown-powered blog built with Angular and complete with a sitemap. While we used the default styling from the template, you can customize it using your favorite UI library. You can also enhance your blog by setting up an API route to dynamically generate open graph images for each blog post.

You can copy or clone the source code from this tutorial to get started with your own blog.

Happy blogging! 🚀


Peter Mbanugo
About the Author

Peter Mbanugo

Peter is a software consultant, technical trainer and OSS contributor/maintainer with excellent interpersonal and motivational abilities to develop collaborative relationships among high-functioning teams. He focuses on cloud-native architectures, serverless, continuous deployment/delivery, and developer experience. You can follow him on Twitter.

Related Posts

Comments

Comments are disabled in preview mode.