Creating a Jekyll-style blog post year archive in Astro

While not included in their default blog template, it is straightforward to create a year-based archive for your posts in Astro.

One of the things I enjoyed about Jekyll was just how much was built in to the core and the common plugins. One of my favorite plugins is jekyll-archives, which allows you to create archive pages for your blog posts based on any type of criteria. On my Jekyll-based site, I had a number of archives, including a year-based archive that allowed you to access, i.e., /blog/2022 to see all posts published in 2022. This was something I wanted to mimic when I converted my website to Astro, and after a bit of investigation, I discovered doing so was pretty straight forward.

Step 1: Create the archive page

Assuming your blog templates live in src/pages/blog, create src/pages/blog/[year].astro. The square brackets around year are intentional and let Astro know that this page has dynamic routes. In this case, you will be replacing year with the four-digit year. Astro knows how to replace year when you define a getStaticPaths() method.

Step 2: Export getStaticPaths()

The getStaticPaths() function is called by Astro to determine how to render dynamic routes. The function must return an array of objects that each contain at least a params key that indicates how to fill in the dynamic portions of the filename and optionally a props key that contains data to make available in Astro.props when the page renders.

To create a year-based archive, you’ll need to ensure that each object in the array specifies the year and the posts for that year. Here is the code to do that:

import { getCollection } from 'astro:content';

export async function getStaticPaths() {

	const posts = await getCollection('blog');
    const postYears = new Map();

    posts.forEach(post => {
        const year = post.data.date.getFullYear();

        if (!postYears.get(year)) {
            postYears.set(year, []);
        }

        postYears.get(year).push(post);
    });


    return [...postYears.entries()].map(([year, posts]) => ({
        params: { year },
        props: { posts }
    }));
};

The first step in this code is to get all of the blog posts in the blog collection. After that, each post is inspected to find its year and then added to the postYears map for easy reference. The last step is to arrange the postYears map into an array of objects containing the years and posts.

This code can be drammatically simplified once the JavaScript Array Grouping proposal is widely implemented, in which case the code would look like this:

import { getCollection } from 'astro:content';

export async function getStaticPaths() {

	const posts = await getCollection('blog');
    const postYears = posts.groupToMap(post => post.getFullYear());

    return [...postYears.entries()].map(([year, posts]) => ({
        params: { year },
        props: { posts }
    }));
};

Step 3: Access the params and props to render

In that same src/pages/blog/[year].astro file, after getStaticPaths(), you can then start with the code to render each page. First, you’ll want to gather the information about the page being rendered from Astro.params and Astro.props, like this:

const { year } = Astro.params;
const { posts } = Astro.props;

Now you have the year and the posts from that year, so all you need to do is render out your list of posts as you would normallly. Here’s an example:

<main>
    <h2>Posts in {year}</h2>
    <section>
        <ul>
            {
                posts.map((post) => (
                    <li>
                        <time datetime={post.data.pubDate.toISOString()}>
                            {post.data.pubDate.toLocaleDateString('en-us', {
                                year: 'numeric',
                                month: 'short',
                                day: 'numeric'
                            })}
                        </time>
                        <a href={`/blog/${post.slug}/`}>{post.data.title}</a>
                    </li>
                ))
            }
        </ul>
    </section>
</main>

You can see a complete example of a year archive page in my astro-jekyll GitHub project.

Conclusion

Even though Astro doesn’t include a lot of the functionality that Jekyll makes easily available, it is dynamic enough to implement anything you might have used in Jekyll. Creating year-based archive pages is just the start. You can use the same technique to create archive pages based on any type of post meta data: tags, categories, titles, etc. All you need to do is follow the same basic structure of creating a dynamically-routed Astro template, filter your blog posts into the groupings that you’d like, and then return that data from getStaticPaths().

Understanding JavaScript Promises E-book Cover

Demystify JavaScript promises with the e-book that explains not just concepts, but also real-world uses of promises.

Download the Free E-book!

The community edition of Understanding JavaScript Promises is a free download that arrives in minutes.