Sorting Articles by Year with Astro
As you might see on the articles page, my articles
are sorted by year. But how did I achieved that? First of
all we have to start with an Astro Content Collection.
Let’s create one inside src/content.config.ts
.
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";
const Article = z.object({
title: z.string(),
description: z.string().optional(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
author: z.string().default("Thomas"),
isDraft: z.boolean().default(true),
});
const articles = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/data/articles" }),
schema: Article,
});
export const collections = { articles };
My articles are stored inside src/data/articles
and the pattern
will match every markdown files, in every subfolders. Then let’s
create src/pages/articles.astro
who will present the articles
split by year. First, let’s fetch the collection.
---
import { getCollection } from "astro:content";
const publishedArticles = (
await getCollection("articles", ({ data }) => {
return data.isDraft !== true;
})
).sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
---
Here, the articles are gathered from the articles
collection, filtered
by their draft status and finally sorted by time. Then we split our articles
by year.
---
// ...
type ArticleSubstract = {
id: number;
pubDate: Date;
title: string;
};
const articlesByYear = new Map();
publishedArticles.forEach((article) => {
const year = article.data.pubDate.getFullYear();
if (!articlesByYear.get(year)) {
articlesByYear.set(year, []);
}
articlesByYear.get(year).push({
id: article.id,
pubDate: article.data.pubDate,
title: article.data.title,
});
});
const flattenedArticles = new Array();
articlesByYear.forEach((articles, year) => {
flattenedArticles.push([year, articles]);
});
---
There must be a better way than to convert my articlesByYear
into an array. However I had a hard time using it as is in
the template part. Speaking of which:
---
import { getCollection } from "astro:content";
const publishedArticles = (
await getCollection("articles", ({ data }) => {
return data.isDraft !== true;
})
).sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
type ArticleSubstract = {
id: number;
pubDate: Date;
title: string;
};
const articlesByYear = new Map();
publishedArticles.forEach((article) => {
const year = article.data.pubDate.getFullYear();
if (!articlesByYear.get(year)) {
articlesByYear.set(year, []);
}
articlesByYear.get(year).push({
id: article.id,
pubDate: article.data.pubDate,
title: article.data.title,
});
});
const flattenedArticles = new Array();
articlesByYear.forEach((articles, year) => {
flattenedArticles.push([year, articles]);
});
---
<header>
<h1>Articles</h1>
</header>
{
flattenedArticles.map((year) => {
const articles = year[1].map((article: ArticleSubstract) => {
return (
<article>
<time>{Intl.DateTimeFormat("en-US").format(article.pubDate)}</time>
<div class="article-title">
<a href={`/articles/${article.id}`}>{article.title}</a>
</div>
</article>
);
});
return (
<section>
<header>
<h2>An {year[0]}</h2>
</header>
<div class="article-title">{articles}</div>
</section>
);
})
}
I loop through every year, and for every year I do the same with the articles.