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:
Let’s get started!
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 yourpackage.json
file, but@analog/content
has a higher version specified as its peer dependency. Change the version ofmarked
to^15.0.0
in yourpackage.json
before you runnpm 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.
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.
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.
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.
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.
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.
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.
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.
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 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.