Motivation
Since my college days, I’ve been thinking of starting my own blog website. Prior to this project, I attempted to create a blog application with React and Golang. However, it becomes progressively complex with the addition of many unneeded features.
As developers, we know that creating a great user experience is key when building a website. And what do users love more than anything? Fast websites that don’t waste their time. With Astro, we can achieve that by shipping less code to the browser.
Why Astro?
In short, Astro is a powerful web framework for building content-driven websites like blogs, marketing, and e-commerce. Astro is best-known for pioneering a new frontend architecture to reduce JavaScript overhead and complexity compared to other frameworks. If you need a website that loads fast and has great SEO, then Astro is for you.
Creating Astro project
Astro has a CLI tool that will help you get up and running in seconds. Run the following command and follow the CLI wizard.
# create a new project with npm
npm create astro@latest
# create a new project based on a GitHub repository’s main branch
npm create astro@latest -- --template <github-username>/<github-repo>
Project Structure
Inside of the project, you’ll see the following folders and files:
/
├── public/
├── src/
│ ├── components/
│ ├── content
│ │ ├── blog/
│ │ └── config.ts
│ ├── layouts/
│ ├── pages/
│ │ └── index.astro
│ ├── styles/
│ └── utils/
└── package.json
Content Collections
A content collection is any top-level directory inside the reserved src/content project directory, such as src/content/newsletter and src/content/authors. Only content collections are allowed inside the src/content directory. This directory cannot be used for anything else.
// 1. Import utilities from `astro:content`
import { z, defineCollection } from "astro:content";
// 2. Define a `type` and `schema` for each collection
const blogCollection = defineCollection({
type: "content",
schema: ({ image }) =>
z.object({
title: z.string(),
description: z.string(),
date: z.date(),
cover: image()
.refine((img) => img.width >= 720, {
message: "Cover image must be at least 720 pixels wide!",
})
.optional(),
tags: z.array(z.string()).default(["others"]),
draft: z.boolean().optional(),
featured: z.boolean().optional(),
}),
});
// 3. Export a single `collections` object to register your collection(s)
export const collections = {
blog: blogCollection,
};
Frontmatter
Frontmatter is the main place to store some important information about the blog post (article). Frontmatter lies at the top of the article and is written in YAML format. Read more about frontmatter and its usage in astro documentation.
Here is the list of frontmatter property for each post.
| Property | Description | Remark |
|---|---|---|
| title | Title of the post. (h1) | required* |
| description | Description of the post. Used in post excerpt and site description of the post. | required* |
| date | Published date. | required* |
| cover | Cover image of the post. | default = none |
| tags | Related keywords for this post. Written in array yaml format. | default = others |
| draft | Mark this post ‘unpublished’. | default = false |
| featured | Whether or not display this post in featured section of home page | default = false |
Only title, description and date fields in frontmatter must be specified.
Title and description (excerpt) are important for search engine optimization (SEO) and thus we encourage to include these in blog posts.
If you omit tags in a blog post (in other words, if no tag is specified), the default tag others will be used as a tag for that post. You can set the default tag in the /src/content/config.ts file.
// src/content/config.ts
export const blogSchema = z.object({
// ---
draft: z.boolean().optional(),
tags: z.array(z.string()).default(["others"]), // replace "others" with whatever you want
// ---
});
Writing a post
Here is the sample frontmatter for a post.
# src/content/blog/sample-post.md
---
title: The title of the post
description: This is the example description of the example post.
data: 2023-01-01
cover: ""
tags:
- some
- example
- tags
featured: true
draft: false
---
Headings
There’s one thing to note about headings. The blog posts use title (title in the frontmatter) as the main heading of the post. Therefore, the rest of the heading in the post should be using h2 ~ h6.
This rule is not mandatory, but highly recommended for visual, accessibility and SEO purposes.
Storing Images for Blog Content
Here are two methods for storing images and displaying them inside a markdown file.
Note! If it’s a requirement to style optimized images in markdown you should use MDX.
Inside src/content/blog/_images directory (recommended)
You can store images inside src/assets/ directory. These images will be automatically optimized by Astro through Image Service API.
Example: Suppose you want to display example.jpg whose path is /src/content/blog/_images/example.jpg.

<!-- Using img tag or Image component won't work ❌ -->
<img src="@assets/images/example.jpg" alt="something">
<!-- ^^ This is wrong -->
Technically, you can store images inside any directory under
src. In here,src/assetsis just a recommendation.
Inside public directory
You can store images inside the public directory. Keep in mind that images stored in the public directory remain untouched by Astro, meaning they will be unoptimized and you need to handle image optimization by yourself.
For these images, you should use an absolute path; and these images can be displayed using markdown annotation or HTML img tag.
Example: Assume example.jpg is located at /public/assets/images/example.jpg.

<!-- OR -->
<img src="/assets/images/example.jpg" alt="something">
Deploy the Site to Cloudflare Pages
Prerequisites
To get started, you will need:
- A Cloudflare account.
- Push your code to a Github repository.
Deploy the site with Git
- Log in to the Cloudflare dashboard and select your account in Account Home > Pages.
- Select Create a new Project and the Connect Git option.
- Select the git project you want to deploy and click Begin setup
- Framework preset: Astro
- Build command: npm run build
- Build output directory: dist
- Click the Save and Deploy button.