Migrating my blog to Astro - Older posts
How do you navigate to older posts on a blog site. The previous approach was using lots of /pageN pages, which got rewritten regularly, causing confusion for search engines. I came up with a simpler strategy.
This is part of a series of posts on how I migrated my blog to Astro
The old Jekyll engine would create these continuous /page2
, /page3
, /page100
pages, that allowed you to navigate further back in time. The problem as I see it with these is that the content of these pages is regenerated every time you add a new post. It’s like using the metaphor of a stack of paper, and each time you write a new post you add it to the top of the stack. That then becomes the new page1, and every page underneath that needs to get renumbered. It’s the complete opposite of how a book or journal is written.
Now it is true that the consumers of a blog don’t need to read it linearly. In fact unless they started reading it when you first started writing, it’s unlikely that anyone will ever do that. Instead they’ll probably just follow along with your latest posts, or maybe they’ll jump directly to a post after finding it through a search engine recommendation or linked from another website.
So I determined to do away with the /pageN
metaphor completely, and replace it with a year-based archive indexing approach:
- Have an /archive index page
- This in turn links to separate per-year index pages
- These each have a list of the posts from that year.
Archive page
This is created via the /src/pages/archive.astro
file.
---
import { getCollection } from "astro:content";
import BaseLayout from "../layouts/BaseLayout.astro";
import getPostsByGroupCondition from "../scripts/getPostsByGroupCondition";
import { DateTime } from "luxon";
import onlyCurrent from "../scripts/filters";
const pageTitle = "Archive";
const posts = (await getCollection("blog")).filter(onlyCurrent);
---
<BaseLayout pageTitle={pageTitle}>
<h1>Posts by year</h1>
<ul>
{
Object.entries(
getPostsByGroupCondition(
posts,
(post) => DateTime.fromISO(post.data.date).year
)
)
.sort(([yearA], [yearB]) => Number(yearB) - Number(yearA))
.map(([year]) => (
<li>
<a href={`/${year}`}>{year}</a>
</li>
))
}
</ul>
</BaseLayout>
We extract a distinct list of years from the blog content collection and use that to generate a list of links to the per-year pages.
You can see the end result here /archive.
Year pages
Whereas the tag index page is at /tags
and each tag page is under that eg. /tags/Azure
, for the posts I realised it made more sense to have them appear to reside within the blog post hierarchy. eg. /2005
.
To achieve this, we create a file /src/pages/[year]/index.astro
. So yes, the subdirectory is named [year]
.
Again, we leverage Astro’s dynamic routes, and infer the year as a parameter from the directory name. Dynamic routes allow you to do that for directories as well as files.
We group all the posts by year, and then ensure that for each year they’re sorted chronologically. A link is then made back to the original post.
---
import { getCollection } from "astro:content";
import getPostsByGroupCondition from "../../scripts/getPostsByGroupCondition";
import { DateTime } from "luxon";
import BaseLayout from "../../layouts/BaseLayout.astro";
import type {
InferGetStaticParamsType,
InferGetStaticPropsType,
GetStaticPaths,
} from "astro";
import onlyCurrent from "../../scripts/filters";
export const getStaticPaths = (async () => {
const posts = (await getCollection("blog")).filter(onlyCurrent);
const years = Object.entries(
getPostsByGroupCondition(posts, (post) =>
DateTime.fromISO(post.data.date).toJSDate().getFullYear()
)
)
.sort(([yearA], [yearB]) => Number(yearB) - Number(yearA))
.map(([year, yearGroup]) => ({
params: { year },
props: {
posts: yearGroup.sort(
(a, b) =>
DateTime.fromISO(a.data.date).toJSDate().getTime() -
DateTime.fromISO(b.data.date).toJSDate().getTime()
),
},
}));
return years;
}) satisfies GetStaticPaths;
type Params = InferGetStaticParamsType<typeof getStaticPaths>;
type Props = InferGetStaticPropsType<typeof getStaticPaths>;
const { year } = Astro.params as Params;
const { posts } = Astro.props as Props;
const pageTitle = `Posts from ${year}`;
const frontmatter = {
description: `Summary list of posts from the year ${year}`,
};
function formatDateWithSuffix(dateString: string) {
const date = DateTime.fromISO(dateString);
const day = date.day;
const suffix =
day % 10 === 1 && day !== 11
? "st"
: day % 10 === 2 && day !== 12
? "nd"
: day % 10 === 3 && day !== 13
? "rd"
: "th";
return `${date.toFormat("d")}${suffix} ${date.toFormat("MMMM")}`;
}
---
<BaseLayout pageTitle={pageTitle} frontmatter={frontmatter}>
<h1>{year}</h1>
<ul class="list-none">
{
posts.map((post) => (
<li>
<time datetime={post.data.date} class="text-gray-500">
{formatDateWithSuffix(post.data.date)}
</time>
- <a href={`/${post.id}`}>{post.data.title}</a>
</li>
))
}
</ul>
</BaseLayout>
Here’s the live version for /2025.
If I wanted, I could do something similar for months in the year. I think it’s fine just at the year level but I don’t think it would be too tricky to add later on.