How to Build a Professional, Low-Cost Personal Website


Here’s how I built a fast, professional personal website without spending much. I’m sharing the exact steps I used so you can copy‑paste, tweak a little, and ship. Mine includes a resume, a body of work, and a blog—but make it yours.

We’ll use Eleventy, a static site generator that pre‑builds everything into plain HTML. That keeps things fast, simple, and hard to break.

Quick note: my repo is private, but if you’d like to peek or reuse parts, I may be open to adding you as a collaborator—just reach out. Repo: github.com/jmorton05/personal-site

The Tech Stack

No frameworks, no heavy CMS—just Markdown files and a quick build step.

I also like using a Makefile for convenience (make build, make serve, make deploy). The steps below stick to plain npx/CLI commands so anyone can follow.

Step 1: Set Up Your Local Environment

Before you can build a site, you need the right tools on your computer.

Install Node.js (LTS). Eleventy runs on Node, so grab the LTS version for your OS.

Verify installation:

node -v
npm -v

Step 2: Create Your Project & Starter Files

Create your project folder and initialize Node:

mkdir my-personal-site
cd my-personal-site
npm init -y
npm install @11ty/eleventy --save-dev

Create folders:

my-personal-site/
|-- blog/
|-- docs/
|-- images/
|-- _includes/

Create these starter files with the content below.

File: .eleventy.js

module.exports = function(eleventyConfig) {
  eleventyConfig.addPassthroughCopy("css");
  eleventyConfig.addPassthroughCopy("images");

  eleventyConfig.addFilter("readableDate", dateObj => {
    return new Date(dateObj).toLocaleDateString('en-US', {
      year: 'numeric',
      month: 'long',
      day: 'numeric'
    });
  });

  // Collection for tag cloud (excludes the structural 'post' tag)
  eleventyConfig.addCollection('tagList', (collection) => {
    const tagSet = new Set();
    collection.getAll().forEach((item) => {
      (item.data.tags || []).forEach((tag) => {
        if (tag !== 'post') tagSet.add(tag);
      });
    });
    return [...tagSet].sort();
  });

  return {
    dir: {
      input: ".",
      includes: "_includes",
      output: "_site"
    },
    markdownTemplateEngine: "njk",
    htmlTemplateEngine: "njk"
  };
};

File: _includes/base.njk

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>How to Build a Professional, Low-Cost Personal Website | Your Name</title>

  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/water.css@2/out/water.css">

  <style>
    .site-header { display: flex; align-items: center; gap: 1.5rem; margin-bottom: 2rem; }
    .profile-image { width: 100px; height: 100px; border-radius: 50%; object-fit: cover; border: 3px solid var(--text-color); }
    .site-header h2 { margin: 0; }
    nav { background-color: var(--background); border: 1px solid var(--text-color); padding: 1.5rem; border-radius: 8px; margin-top: 1rem; }
    nav a { text-decoration: none; font-weight: bold; margin-right: 1.5rem; }
    nav a[aria-current="page"] { text-decoration: underline; text-decoration-thickness: 3px; }
    .social-links { display: flex; gap: 1.5rem; margin-top: 1.5rem; padding-left: 0.5rem; }
    .social-links a { display: inline-flex; align-items: center; gap: 0.5rem; text-decoration: none; font-weight: bold; color: var(--text-color); opacity: 0.8; }
    .social-links a:hover { opacity: 1; text-decoration: underline; }
    .social-links svg { width: 24px; height: 24px; }
  </style>
</head>
<body>

  <header>
    <div class="site-header">
      <img src="/images/profile.jpg" alt="Headshot of Your Name" class="profile-image">
      <h2>Your Name // Your Title</h2>
    </div>

    <nav>
      <a href="/" >Home</a>
      <a href="/resume/" >Resume</a>
      <a href="/body-of-work/" >Body of Work</a>
      <a href="/blog/" >Blog</a>
    </nav>

    <div class="social-links">
      <a href="https://www.linkedin.com/in/YourUsername" target="_blank" rel="noopener noreferrer" title="My LinkedIn Profile">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"/></svg>
        <span>LinkedIn</span>
      </a>
      <a href="https://github.com/YourUsername" target="_blank" rel="noopener noreferrer" title="My GitHub Profile">
        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.108-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.91 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"/></svg>
        <span>GitHub</span>
      </a>
    </div>

  </header>

  <main>
    <h1>How to Build a Professional, Low-Cost Personal Website</h1>
    
  </main>

  <footer>
    <hr>
    <p>&copy; 2025 Your Name. Generated with Eleventy.</p>
  </footer>
</body>
</html>

File: docs/index.md

---
layout: base.njk
title: Home
permalink: /
---

## About Me
(Add your "About Me" and "Core Competencies" here.)

---

## Where to next?

* **[View my concise summary resume →](/resume/)**
* **[See a detailed history of my projects →](/body-of-work/)**
* **[Read my thoughts on the blog →](/blog/)**

File: docs/resume.md

---
layout: base.njk
title: Resume
permalink: /resume/
---

(Add your concise, one-page summary resume content here.)

---

**[View my detailed project history & body of work →](/body-of-work/)**

File: docs/body-of-work.md

---
layout: base.njk
title: Detailed Body of Work
permalink: /body-of-work/
---

(Add your full, detailed career history, projects, and feedback here.)

---

**[← View the concise summary resume](/resume/)**

File: blog/index.md

---
layout: base.njk
title: Blog
---

Here are my latest posts, thoughts, and articles.

<ul class="post-list"><li class="post-list-item">
    <h3><a href="/blog/how-to-build-a-professional-low-cost-personal-website/">How to Build a Professional, Low-Cost Personal Website</a></h3>
    <time datetime="2025-12-01T00:00:00.000Z">December 1, 2025</time>
    <p>Notes from my initial site setup—how I bootstrapped this fast, low-cost personal site with Eleventy + Firebase, and what I’d do again.</p>
  </li><li class="post-list-item">
    <h3><a href="/blog/reflections-and-next-steps/">Reflections on 21 Years and What&#39;s Next</a></h3>
    <time datetime="2025-11-13T00:00:00.000Z">November 13, 2025</time>
    <p>After an amazing 21-year journey at Target, I&#39;m sharing my reflections and looking forward to the next chapter.</p>
  </li></ul>

File: blog/first-post.md

---
layout: post.njk
title: My First Post
date: 2025-11-13
tags:
  - post
description: A short summary of my first post for the list page.
---

Welcome to my new blog!

(Add your blog post content here.)

File: _includes/post.njk

---
layout: base.njk
---


<hr>
<a href="/blog/">&larr; Back to all posts</a>

File: .gitignore

# Dependencies
/node_modules

# Build output
/_site

# Firebase local cache
/.firebase

Themes in Eleventy (Starters & Overrides)

Eleventy doesn’t come with a default theme (on purpose). You’ve got two good options:

Browse Community Themes

Option A — Use a Community Starter

If you want to move fast, start from a community theme. The official eleventy-base-blog and other starters give you layouts, pagination, and styles out of the box.

Clone a starter without its git history using degit:

npx degit 11ty/eleventy-base-blog my-personal-site
cd my-personal-site
npm install
npx @11ty/eleventy --serve

Customize by editing files in _includes/ (e.g., base.njk, post.njk) and the CSS (css/ or scss/). Keep the starter’s structure but replace branding, colors, and components.

Implement a Starter (Step‑by‑Step)

  1. Review the starter’s .eleventy.js for passthroughs, collections, and filters.
  2. Keep or adapt its layouts in _includes/ (e.g., base.njk, post.njk, components/).
  3. Customize styles via its CSS/SCSS or by overriding CSS variables.
  4. Run locally and verify /, /blog/, and any tag pages render:
npx @11ty/eleventy --serve
  1. Trim what you don’t need (sample posts, partials) and commit.

Option B — Roll Your Own and Override

If you like more control, keep the simple Water.css base and layer your own styles and layouts. To brand and theme it further:

:root {
  --background: #0b0e14;
  --text-color: #e6e6e6;
  --link-color: #64b5f6;
}
@media (prefers-color-scheme: dark) {
  :root {
    /* Optional: tweak dark mode separately */
  }
}

Tip: Whether you start from a theme or build your own, Eleventy’s “data cascade” means your local copies of templates and includes are the source of truth. You don’t “install” a theme at runtime—you keep the templates in your repo and modify them as needed.

What I’d Do Again

Short version: this stack works and stays out of the way.

Making This Even Easier

I’m exploring small quality‑of‑life tweaks so I publish faster:

# example script idea
npm run new:post "My New Post"

Step 3: Run Your Site Locally

You're all set! Run the local server to see your site in action.

npx @11ty/eleventy --serve

Open your browser to http://localhost:8080. As you save changes, the site auto-refreshes.

Now, personalize:

Step 4: Deploy Your Site to Firebase

Create a Firebase project in the Firebase Console. Then install the CLI:

npm install -g firebase-tools
firebase login

Initialize Hosting in your project and choose your project, with public directory set to _site:

firebase init

Build and deploy:

npx @11ty/eleventy
firebase deploy

Firebase will output a Hosting URL (e.g., your-project.web.app).

Step 5: Add a Custom Domain (Optional)

Buy a domain (e.g., at Cloudflare). In Firebase Hosting, click "Add custom domain" and follow the prompts. Add the provided DNS records in Cloudflare and set A-records to DNS only (grey cloud). After propagation (typically 30–60 minutes), verify in Firebase. Optionally add a www CNAME that redirects to the apex domain.

Step 6: How to Add a New Blog Post

Create a new file under blog/ and include the post tag:

---
layout: post.njk
title: My Awesome Second Post
date: 2025-11-14
tags:
  - post
description: A summary of my second post.
---

(Write your new post content here...)

Build and deploy:

npx @11ty/eleventy
firebase deploy

That's it—your site updates with the new post.


← Back to all posts