Rebuilding Your Blog with Astro: A Step-by-Step Guide

Rebuilding Your Blog with Astro: A Step-by-Step Guide

Takahiro Iwasa
Takahiro Iwasa
9 min read
Astro Tailwind CSS

Introduction

Astro is a modern Static Site Generator (SSG) gaining popularity for its innovative features and performance benefits. Let’s dive into the setup process and explore how to rebuild your blog using Astro.

Features of Astro

Astro simplifies web development by focusing on performance and flexibility. Two standout features are:

  • Astro Islands: Isolates components into islands, enabling you to use multiple frameworks like React, Vue within the same project.
  • Zero JS, by default: Optimizes performance by converting most JavaScript to pure HTML at build time.

For detailed explanations, visit Astro’s official documentation.

Setting Up Astro

Astro makes the setup process seamless. Follow these steps to kickstart your Astro blog:

1. Initializing the Project

Run the following command to create a new Astro project:

npm create astro@latest

2. Configuration Prompts

During the setup, you will be prompted to make choices for your project. Below is an example walkthrough:

 astro   Launch sequence initiated.

   dir   Where should we create your new project?
         ./

  tmpl   How would you like to start your new project?
         Use blog template
 ██████  Template copying...

  deps   Install dependencies?
         Yes
 ██████  Installing dependencies with npm...

    ts   Do you plan to write TypeScript?
         Yes

   use   How strict should TypeScript be?
         Strict
 ██████  TypeScript customizing...

   git   Initialize a new git repository?
         Yes
 ██████  Git initializing...

  next   Liftoff confirmed. Explore your project!
         Run npm run dev to start the dev server. CTRL+C to stop.
         Add frameworks like react or tailwind using astro add.

         Stuck? Join us at https://astro.build/chat

╭─────╮  Houston:
│ ◠ ◡ ◠  Good luck out there, astronaut! 🚀
╰─────╯

3. Starting the Development Server

After the setup is complete, start the development server:

npm run dev

The default server will be available at http://localhost:4321/.

Content Collections

Overview

Contents such as blog posts are managed as collections in Astro. For a comprehensive guide, refer to the official page on Content Collections.

Create any directory under src/content/ and place Markdown files inside it. MDX is also supported. Below is an example of a blog post file (blog/first-post.md):

---
title: 'First post'
description: 'Lorem ipsum dolor sit amet'
pubDate: 'Jul 08 2022'
heroImage: '/blog-placeholder-3.jpg'
---

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
...

Collection Definition

Astro allows users to define metadata (frontmatter) and types for content within src/content/config.ts using the Zod schema validation library. Although optional, defining collections is highly recommended for its benefits, such as schema validation and TypeScript typings.

The src/content/config.ts file is optional. However, choosing not to define your collections will disable some of their best features, like frontmatter schema validation or automatic TypeScript typings.

Here is an example of a basic config.ts setup:

import { defineCollection, z } from 'astro:content';

const blog = defineCollection({
  // Type-check frontmatter using a schema
  schema: z.object({
    title: z.string(),
    description: z.string(),
    // Transform string to Date object
    pubDate: z.coerce.date(),
    updatedDate: z.coerce.date().optional(),
    heroImage: z.string().optional(),
  }),
});

export const collections = { blog };
KeyTypeMandatory
titlestringYes
descriptionstringYes
pubDateDateYes
updatedDateDateNo
heroImagestringNo

For more details on defining collections, see the official guide.

Using Collections

Astro provides functions such as getCollection and getEntry to access content collections.

For instance, frontmatter is stored within the data object, allowing easy access:

---
import { getEntry } from 'astro:content';

const blogPost = await getEntry('blog', 'welcome');
---

<h1>{blogPost.data.title}</h1>
<p>{blogPost.data.description}</p>

By defining and using collections effectively, developers can simplify content management while ensuring a consistent structure across their projects.

Pages

Routing

Astro uses file-based routing, which means files in the pages directory automatically map to specific URLs. Here’s an example of how the routing works:

FileURL
pages/index.astrohttps://<YOUR_DOMAIN>/
pages/about.astrohttps://<YOUR_DOMAIN>/about/
pages/blog/index.astrohttps://<YOUR_DOMAIN>/blog/

Additionally, dynamic routing is supported. This is achieved by using [...NAME] as a directory or file name. For example, in a newly set up project, blog/[...slug].astro is configured for dynamic routing.

The [...slug] part is handled by the getStaticPaths function within the file, as seen in the following example:

---
import { type CollectionEntry, getCollection } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map((post) => ({
    params: { slug: post.slug },
    props: post,
  }));
}
type Props = CollectionEntry<'blog'>;

const post = Astro.props;
const { Content } = await post.render();
---

<BlogPost {...post.data}>
  <Content />
</BlogPost>

Page Structure

Astro’s pages, layouts, and components follow a structure familiar to modern frontend setups. Components can also be placed directly within pages, but the recommended approach is to keep reusable elements in the components directory.

Below is an example of a typical page structure:

src/
├── components/
│   ├── BaseHead.astro
│   ├── Header.astro
│   └── Footer.astro
├── layouts/
│   └── BlogPost.astro
├── pages/
│   ├── index.astro
│   ├── about.astro
│   └── blog/
│       ├── index.astro
│       └── [...slug].astro

Astro Components

Astro components are written using a syntax similar to React’s JSX/TSX. If you are familiar with React, transitioning to Astro’s syntax should feel natural. Here is a snippet of the pages/index.astro file:

---
import BaseHead from "../components/BaseHead.astro";
import Header from "../components/Header.astro";
import Footer from "../components/Footer.astro";
import { SITE_TITLE, SITE_DESCRIPTION } from "../consts";

// This will cause an error.
// const main = document.querySelector('main');
---

<!doctype html>
<html lang="en">
  <head>
    <BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
  </head>
  <body>
    <Header title={SITE_TITLE} />
    <main>
      <h1>🧑‍🚀 Hello, Astronaut!</h1>
      <!-- Additional content -->
    </main>
    <Footer />

    <script>
      // Use document, windoww, etc. here.
      const main = document.querySelector('main');
    </script>
  </body>
</html>

Important Considerations

Astro’s frontmatter (--- block) is processed on the server using Node.js. This means that browser-specific objects like document or window are not available in the frontmatter. Attempting to use them there will result in an error:

 error   document is not defined
  Hint:
    Browser APIs are not available on the server.

    Move your code to a <script> tag outside of the frontmatter, so the code runs on the client.

    See https://docs.astro.build/en/guides/troubleshooting/#document-or-window-is-not-defined for more information.

For browser-based operations, ensure they are wrapped in <script> tags, as shown in the example above.

Rendering Content

To render content dynamically, Astro provides the Content component, which can be extracted from CollectionEntry. In the example below, Content is rendered within the pages/blog/[...slug].astro file:

---
import { type CollectionEntry, getCollection } from 'astro:content';
import BlogPost from '../../layouts/BlogPost.astro';

export async function getStaticPaths() {
	const posts = await getCollection('blog');
	return posts.map((post) => ({
		params: { slug: post.slug },
		props: post,
	}));
}
type Props = CollectionEntry<'blog'>;

const post = Astro.props;
const { Content } = await post.render();
---

<BlogPost {...post.data}>
	<Content />
</BlogPost>

Alternatively, you can directly import Markdown files and use them as components:

---
import { Content as PromoBanner } from '../components/promoBanner.md';
---

<h2>Today's promo</h2>
<PromoBanner />

Astro’s combination of static and dynamic routing, flexible component system, and intuitive rendering process makes it a powerful tool for building scalable and performant websites.

Building

Running npm run build or astro build will generate build artifacts in the dist/ directory. These artifacts represent your fully built site, ready for deployment. Below is an example of the generated output right after setup:

As you can see, no JavaScript is generated, keeping the build lightweight and highly optimized.

Example Build Output

tree dist/ --dirsfirst

dist/
├── about
│   └── index.html
├── blog
│   ├── first-post
│   │   └── index.html
│   ├── markdown-style-guide
│   │   └── index.html
│   ├── second-post
│   │   └── index.html
│   ├── third-post
│   │   └── index.html
│   ├── using-mdx
│   │   └── index.html
│   └── index.html
├── fonts
│   ├── atkinson-bold.woff
│   └── atkinson-regular.woff
├── blog-placeholder-1.jpg
├── blog-placeholder-2.jpg
├── blog-placeholder-3.jpg
├── blog-placeholder-4.jpg
├── blog-placeholder-5.jpg
├── blog-placeholder-about.jpg
├── favicon.svg
├── index.html
├── rss.xml
├── sitemap-0.xml
└── sitemap-index.xml

Key Insights

  • HTML Output: Each page is pre-rendered into static HTML files.
  • No JavaScript: Astro minimizes JavaScript unless explicitly added via frameworks or components.
  • Static Assets: Fonts and images are included as-is, ensuring no unnecessary processing for public resources.

Astro’s build process prioritizes speed and efficiency, making it ideal for performance-critical websites.

Other Topics

Image Component

Astro provides an Image component for optimized image handling. Images placed under the src/ directory can be transformed and converted to WebP format during the build process, improving performance.

Recommendations

  • Place images under src/ for optimization.
  • Avoid using public/ for local images if transformations are required.

For more information, refer to the Astro Images Guide.

Tailwind CSS Integration

Astro supports Tailwind CSS through its integration system. To install Tailwind, run:

npx astro add tailwind

This process:

  1. Installs @astrojs/tailwind and the required tailwindcss package.
  2. Generates a minimal tailwind.config.mjs configuration file.
  3. Adds Tailwind to the integrations array in astro.config.mjs.

Example Tailwind Usage

<body>
  <Header />
  <main class="ml-2">
    <!-- Your content -->
  </main>
  <Footer />
</body>

For details, visit the Tailwind Integration Guide.

IDE Support

Astro offers IDE plugins to enhance the development experience:

IDEProvided ByStability
VS CodeAstroStable
JetBrainsJetBrainsUnstable impression as of Nov 2023

Leverage these plugins for syntax highlighting, IntelliSense, and error detection.

Conclusion

Astro’s emphasis on performance, simplicity, and developer-friendly tools ensures an enjoyable and efficient experience for building static websites. The combination of features like Astro Islands and Zero JS by default makes it a trendsetter among modern static site generators.

I hope this post inspires you to explore Astro for your next project.

Happy Coding! 🚀

Takahiro Iwasa

Takahiro Iwasa

Software Developer at KAKEHASHI Inc.
Involved in the requirements definition, design, and development of cloud-native applications using AWS. Now, building a new prescription data collection platform at KAKEHASHI Inc. Japan AWS Top Engineers 2020-2023.