Migrating my portfolio to Astro
The old version of this site was a Vue SPA: a single empty div, a router with
one route, and a bundle of JavaScript whose only real job was to render text
that never changes. It worked, but every visit paid for a framework it did not
need, and publishing anything meant writing Vue components by hand.
What changed
The rebuild runs on Astro v7 and stays fully static:
- the home page is the same design, ported to plain CSS without any client framework;
- blog posts are plain markdown files in the repo;
astro buildoutputs HTML that any static host can serve as-is.
Astro ships zero JavaScript by default, which fits a portfolio perfectly. The only scripts left on the page are the ones used for design purposes: the cursor-following gradient and the fullscreen menu toggle.
The content pipeline
Posts live in src/content/blog/ with a YYYY-MM-DD-slug.md naming
convention, so sorting by filename is sorting by date. Every file is validated
at build time by a Zod schema:
import { defineCollection, z } from 'astro:content';
import { glob } from 'astro/loaders';
const blog = defineCollection({
loader: glob({ pattern: '**/[^_]*.md', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
description: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
stack: z.array(z.string()).default([]),
repo: z.string().url().optional(),
demo: z.string().url().optional(),
draft: z.boolean().default(false)
})
});
export const collections = { blog };
Each post is automatically turned into a static page at build time using
getStaticPaths. The date is removed from the URL to keep links clean, and
code snippets are automatically highlighted with Shiki