Created: December 1, 2025
Notes from my initial site setup—how I bootstrapped this fast, low-cost personal site with Eleventy + Firebase, and what I’d do again.
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
- Content: Eleventy (11ty) — a fast, simple static site generator
- Hosting: Firebase Hosting — generous free tier and global CDN
- Domain (optional): Cloudflare (or any registrar)
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>© 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'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'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/">← 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:
- Start from a community theme/starter (fastest), or
- Build your own shell and override styles/layouts (most control).
Browse Community Themes
- Eleventy Starter Projects (official directory): 11ty Starter Directory
- eleventy-base-blog (official blog starter): eleventy-base-blog
- Awesome Eleventy (curated list): Awesome Eleventy (GitHub)
- GitHub Topic – eleventy-starter: GitHub: eleventy-starter topic
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)
- Review the starter’s
.eleventy.jsfor passthroughs, collections, and filters. - Keep or adapt its layouts in
_includes/(e.g.,base.njk,post.njk,components/). - Customize styles via its CSS/SCSS or by overriding CSS variables.
- Run locally and verify
/,/blog/, and any tag pages render:
npx @11ty/eleventy --serve
- 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:
- Override CSS variables used by Water.css (affects colors site‑wide):
:root {
--background: #0b0e14;
--text-color: #e6e6e6;
--link-color: #64b5f6;
}
@media (prefers-color-scheme: dark) {
:root {
/* Optional: tweak dark mode separately */
}
}
- Keep structural HTML in
_includes/base.njkand content templates in_includes/post.njkso changing the theme is just swapping styles and small markup. - Add custom components (cards, tag chips) as Nunjucks macros under
_includes/components/and reuse them across pages.
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.
- Private repo: I prefer keeping my code private and inviting collaborators as needed.
- Custom themes: since I’m comfortable with CSS, owning the styles is easy and fun.
- Firebase Hosting: simple deploys, fast CDN, generous free tier.
- Eleventy: fast builds, minimal config, great for Markdown‑first sites.
- Markdown content: writing posts and docs in
.mdkeeps me moving.
Making This Even Easier
I’m exploring small quality‑of‑life tweaks so I publish faster:
- New‑post helper: add a command to scaffold front matter and filenames.
# example script idea
npm run new:post "My New Post"
- Editor snippets: VS Code snippets for post front matter and project pages.
- Image drops: keep an
images/drop zone and reference with absolute paths. - One‑command deploy: stick with
make deployto build + push in one go.
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:
- Add your headshot to
/images/profile.jpg - Update
_includes/base.njkwith your name, title, and social links - Fill in
docs/index.md,docs/resume.md, anddocs/body-of-work.md - Write your first post in
blog/first-post.md
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
- Feature: Hosting
- Use an existing project
- Public directory:
_site - Single-page app rewrite: N
- GitHub deploys: N
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.