← Back to blog

How I Built My Personal Website with Astro + Cloudflare Pages for Free

Every developer should have a personal website. Not because it guarantees job offers, but because it’s yours — a corner of the internet that represents who you are and what you build.

I recently launched gueroverde.com and the total cost was $10.46/year (just the domain). Everything else was free. Here’s exactly how I did it.

The Stack

  • Astro — Static site generator, perfect for blogs and personal sites
  • Cloudflare Pages — Free hosting with global CDN, auto-deploys on every push via Wrangler
  • Cloudflare — Domain registration at cost price (~$10.46/year for a .com)
  • MDX — Write blog posts in Markdown with component support

Why Astro?

I considered a few options: plain HTML, Next.js, or Gatsby. Astro won because:

  1. Zero JS by default — Ships no JavaScript unless you need it. Blazing fast.
  2. Built-in i18n support — I wanted both English and Spanish versions.
  3. Content Collections — Blog posts as MDX files, typed and structured.
  4. Deploy anywhere — Cloudflare Pages, Vercel, Netlify, GitHub Pages.

If you’re a React/Vue dev, Astro feels immediately familiar. Components, layouts, props — same concepts.

Step 1: Create the Astro Project

npm create astro@latest my-site
cd my-site
npm install
npm run dev

Astro’s setup wizard will ask about templates and TypeScript. I went with a minimal template and enabled TypeScript.

Step 2: Structure for i18n

I organized my pages like this:

src/
  pages/
    index.astro          # redirects to /en/
    en/
      index.astro
      blog/
        index.astro
        [slug].astro
    es/
      index.astro
      blog/
        index.astro
        [slug].astro
  content/
    blog/
      en/
        my-first-post.mdx
      es/
        mi-primer-post.mdx

A simple src/i18n/utils.ts file handles translations:

export const languages = {
  en: 'English',
  es: 'Español',
};

export const defaultLang = 'en';

export const ui = {
  en: {
    'nav.home': 'Home',
    'nav.blog': 'Blog',
    'nav.about': 'About',
  },
  es: {
    'nav.home': 'Inicio',
    'nav.blog': 'Blog',
    'nav.about': 'Sobre mí',
  },
} as const;

Step 3: Add SEO Basics

This is often skipped and it’s a mistake. Add these to your BaseLayout.astro:

---
const { title, description, canonicalURL } = Astro.props;
---
<head>
  <title>{title}</title>
  <meta name="description" content={description} />
  <link rel="canonical" href={canonicalURL} />

  <!-- Open Graph -->
  <meta property="og:title" content={title} />
  <meta property="og:description" content={description} />
  <meta property="og:url" content={canonicalURL} />
  <meta property="og:type" content="website" />

  <!-- Twitter Card -->
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content={title} />
  <meta name="twitter:description" content={description} />
</head>

Also install the sitemap integration:

npx astro add sitemap

And add your site URL to astro.config.mjs:

export default defineConfig({
  site: 'https://yourdomain.com',
  integrations: [sitemap()],
});

Step 4: Deploy to Cloudflare Pages

First, install the Cloudflare adapter:

npm install @astrojs/cloudflare

Configure astro.config.mjs to use it:

import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
import sitemap from '@astrojs/sitemap';

export default defineConfig({
  site: 'https://yourdomain.com',
  output: 'static',
  adapter: cloudflare(),
  integrations: [sitemap()],
});

Create a wrangler.jsonc file at the project root:

{
  "name": "gueroverde-site",
  "compatibility_date": "2024-01-01",
  "pages_build_output_dir": "dist"
}

Now connect your repo in the Cloudflare Pages dashboard:

  1. Go to dash.cloudflare.comWorkers & PagesCreate
  2. Select Connect to Git and authorize your GitHub repository
  3. Set the build command to npm run build and output directory to dist
  4. Click Save and Deploy

That’s it — every push to main auto-deploys. No GitHub Actions, no YAML files. Cloudflare detects the changes and rebuilds your site.

Step 5: Buy a Custom Domain (Optional but Worth It)

I used Cloudflare Registrar. Reasons:

  • Sells domains at wholesale cost — no markup
  • gueroverde.com was $10.46/year
  • Renewals at the same price forever (no bait-and-switch)
  • Free DNSSEC and WHOIS privacy included

Alternatives: Namecheap is also great and includes free WHOIS privacy.

Once you buy the domain, add it as a custom domain in your Cloudflare Pages project and DNS is configured automatically.

Step 6: Submit to Google Search Console

Don’t skip this. Go to search.google.com/search-console, add your domain, verify via DNS (Cloudflare makes this a 2-click process), and submit your sitemap:

https://yourdomain.com/sitemap-index.xml

This is how Google discovers your site. Without it, you could wait months to be indexed.

The Result

A fully custom personal site with:

  • ✅ English + Spanish versions
  • ✅ Blog with MDX support
  • ✅ Auto-deploy on every push
  • ✅ SEO-ready (sitemap, meta tags, OG, robots.txt, RSS)
  • ✅ Custom domain with SSL
  • ✅ Cloudflare’s global CDN — your site loads fast everywhere in the world
  • Total cost: $10.46/year

Cloudflare Pages is completely free: unlimited requests, 500 builds/month, and unlimited bandwidth. You only pay for the domain.

What’s Next

I’m planning to add:

  • A “Services/Hire Me” section for freelance consulting
  • More technical posts (AWS architecture, Laravel patterns)
  • Affiliate links for tools I actually use and recommend

If you’re a developer without a personal site, there’s no excuse anymore. The stack is free, the tooling is excellent, and the whole thing can be live in an afternoon.


Have questions about any of these steps? Drop a comment or reach out — I’m happy to help.