
How I Turned My PDF CV Into a Personal Website
Getting Started With Astro (and a Handy Blog Template)
My first step was running npm create astro@latest
using a
blog template .
This template gave me a great starting point, so I didn’t have to set everything up by hand.
I got a nice file structure, some starter styles, and a built-in blog layout.
It’s perfect if you want to hit the ground running without reinventing the wheel,
and I chose not to let AI do everything so I could understand how things work along the way.
Spicing It Up With Tailwind To make everything look slick, I added Tailwind . If you’ve never used it, think of Tailwind as a big box of Lego bricks for styling—you can quickly build up your design without diving into complicated CSS files. Changing margins, colors, or fonts? Easy-peasy. Tailwind made my site feel modern and cohesive with minimal fuss.
Turning a PDF CV Into a Proper Web Page
My old CV was a static PDF, which felt pretty stale. Instead of just embedding it (yawn),
I stored all the info—work history, education, and skills—in a src/content/cv/cv.json
file.
{
"workExperiences": [
{
"company": "GINDUMAC",
"position": "CTO",
"description": "Responsible for all technology assets of the company. The platform is based on AWS/GCP services and multiple programming languages, mainly Golang, Typescript, and Python.",
"startDate": "2019-09-01"
},
],
"certifications": [
{
"name": "AWS Certified Solutions Architect - Associate",
"date": "2021-01-01",
},
]
}}
And the following src/content/config.ts
file:
import { defineCollection, z } from "astro:content";
const blog = defineCollection({
type: "content",
// Type-check frontmatter using a schema
schema: z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: z.string(),
tags: z.array(z.string()).default([]),
}),
});
const cv = defineCollection({
type: "data",
schema: z.object({
workExperiences: z.array(
z.object({
company: z.string(),
position: z.string(),
startDate: z.coerce.date(),
endDate: z.coerce.date().optional(),
description: z.string().optional(),
})
),
certifications: z.array(
z.object({
name: z.string(),
issuer: z.string(),
date: z.coerce.date(),
certificateUrl: z.string().optional(),
})
),
}),
});
export const collections = { blog, cv };
This separated my data from the design, making it super simple to update down the road without touching any layout code. Now, my home page isn’t just a blog index; it’s also my digital resume. Anyone who lands there can instantly see who I am, what I’ve done, and what I’m all about—all in a format that’s easy to read and, most importantly, easy for me to keep updated.
I did try using the browser’s print function to offer a “Download CV as PDF” button to users, but it came out quite ugly. I ended up just hosting the original PDF in a GCP bucket since I was already using GCP. Maybe I’ll automate this file upload with a GitHub Action in the future.
I asked Cursor Copilot to implement this functionality based on the JSON structure, and it worked after some manual adjustments and guidance. Cursor sped up the process significantly, maybe by 2 to 4 times.
Upgrading the Blog Section I didn’t stop there. I wanted my blog to be more than just a scrolling list of posts, so I added a filter-by-tag feature. Now, if people are interested in topics like web development, Astro, Tailwind, or design, they can easily zero in on the posts that matter to them. It’s all about making the reading experience smoother and more personal.
Again, I asked Cursor for this, and it worked like a charm. The code might not be the cleanest, but it works fine. All filtering is done client-side, as I don’t expect to write enough posts for this to become a performance issue.
Deployment Made Simple I’m currently using a self-hosted solution with Coolify, but if you don’t want to maintain a server yourself, I’d recommend something like Vercel or Netlify. Most of these platforms have generous free tiers, which is perfect for personal projects like this.
Wrapping Up This project taught me that it’s entirely possible to transform an old PDF resume into a dynamic, good-looking personal site. Astro’s templates saved me a ton of time and taught me the basics of the framework, Tailwind made styling painless, and keeping my CV data in JSON means I can update it whenever I want. Plus, adding a simple tag filter to the blog makes the whole site feel much more user-friendly. AI has proven really useful for me on frontend projects, allowing me to explore various options quickly and then tweak the best one to work properly in my codebase.
If your dusty old resume PDF could use some fresh air, consider turning it into a live, dynamic site. It’s not as hard as you might think, and the end result is something that can grow and adapt along with your career. Go ahead—give it a shot!