How to Build a Resume or CV Website with Jekyll
Create a professional online resume or CV with Jekyll — structured YAML data, clean printable layout, PDF export, and free hosting on GitHub Pages.
An online resume is no longer optional for developers, designers, academics, or anyone in a technical field. When a recruiter, client, or collaborator searches your name, they will find your GitHub profile and your personal site — and what they see on that site shapes their first impression before any conversation begins.
Jekyll is an excellent foundation for an online resume. You get full design control without wrestling with WordPress or Wix, the site is free to host on GitHub Pages, and updating your resume is as simple as editing a YAML file and pushing a commit. This guide walks through building a professional Jekyll resume from scratch, including a data-driven structure, a clean printable layout, print-to-PDF CSS, and practical tips for keeping your resume sharp.
Why Jekyll for a resume?
Before diving into the build, it is worth understanding why Jekyll beats the alternatives.
Full design control. Resume builders like Canva or Resumé.io give you polished templates but lock you into their system. With Jekyll, your resume is HTML and CSS you control completely. You can match your personal brand exactly.
Free hosting forever. GitHub Pages hosts Jekyll sites at yourusername.github.io at no cost. No monthly fees, no ads, no expiry. Add a custom domain for a few dollars per year and your resume lives at yourdomain.com/resume/.
Data-driven updates. The approach in this guide stores all resume content in a single YAML file. When you change jobs, add a certification, or update a skill, you edit one file. The HTML renders automatically. You never touch template code to update your information.
Version control. Every update to your resume is a Git commit. You can see exactly what changed, when, and roll back if you make a mistake. You can maintain multiple branches for different target roles or industries.
Printable to PDF. With a proper @media print CSS block, your resume prints cleanly from any browser. Visitors can save a perfect PDF in one click — no separate Word file to maintain.
Structuring resume data in YAML
Store all resume content in _data/resume.yml. This separates your data from your presentation layer.
# _data/resume.yml
name: "Marcus Webb"
title: "Senior Software Engineer"
email: "marcus@example.com"
phone: "+44 7700 900123"
website: "https://marcuswebb.io"
github: "marcuswebb"
linkedin: "marcus-webb"
location: "London, UK"
summary: >
Senior software engineer with 8 years building scalable web
applications and developer tooling. Specialised in Ruby, JavaScript,
and static site architecture. Passionate about performance, open
source, and clear documentation.
experience:
- company: "Acme Corp"
role: "Senior Software Engineer"
period: "Jan 2022 – Present"
location: "London, UK"
highlights:
- "Led migration from monolith to microservices, cutting deployment time by 60%"
- "Architected event-driven notification system handling 2M messages/day"
- "Mentored 4 junior engineers through structured weekly 1:1s"
- "Implemented GitHub Actions CI/CD pipeline, reducing build failures by 40%"
- company: "Beta Startup"
role: "Software Engineer"
period: "Mar 2019 – Dec 2021"
location: "Remote"
highlights:
- "Built customer-facing REST API serving 500k requests per day"
- "Reduced median page load from 4.2s to 0.8s through lazy loading and CDN tuning"
- "Owned the entire checkout flow from prototype to 1,000+ daily transactions"
- company: "Gamma Agency"
role: "Junior Developer"
period: "Sep 2017 – Feb 2019"
location: "Edinburgh, UK"
highlights:
- "Delivered 12 client sites on time and within budget"
- "Introduced automated testing, reducing QA time by 30%"
education:
- institution: "University of Edinburgh"
degree: "BSc Computer Science"
period: "2013 – 2017"
grade: "First Class Honours"
notes: "Dissertation: Adaptive caching strategies for static site generators"
skills:
- category: "Languages"
items: ["Ruby", "JavaScript", "TypeScript", "Python", "SQL", "Bash"]
- category: "Frameworks & Libraries"
items: ["Rails", "React", "Next.js", "Node.js", "Jekyll", "Sinatra"]
- category: "Infrastructure"
items: ["AWS", "Docker", "Kubernetes", "GitHub Actions", "Terraform"]
- category: "Databases"
items: ["PostgreSQL", "Redis", "Elasticsearch", "SQLite"]
projects:
- name: "JekyllHub"
url: "https://jekyllhub.com"
description: "Jekyll themes marketplace serving 10,000+ monthly visitors. Built with Jekyll, SCSS, and vanilla JS."
tech: ["Jekyll", "SCSS", "JavaScript"]
- name: "FeedParser"
url: "https://github.com/marcuswebb/feed-parser"
description: "Ruby gem for parsing and normalising RSS and Atom feeds. 800+ GitHub stars."
tech: ["Ruby", "RSpec"]
certifications:
- name: "AWS Certified Solutions Architect – Associate"
issuer: "Amazon Web Services"
year: 2023
- name: "Certified Kubernetes Administrator"
issuer: "Cloud Native Computing Foundation"
year: 2022
The > block scalar for summary lets you write multi-line text in YAML that renders as a single paragraph. Anything in _data/ is available throughout your site as site.data.resume.
Creating the resume layout
A resume page benefits from a minimal layout that strips away your site’s standard header and footer. Create _layouts/resume.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ site.data.resume.name }} — Resume</title>
<meta name="description" content="{{ site.data.resume.title }} based in {{ site.data.resume.location }}">
<link rel="stylesheet" href="{{ '/assets/css/resume.css' | relative_url }}">
<meta name="robots" content="index, follow">
</head>
<body class="resume-page">
<div class="resume">
{{ content }}
</div>
<script>
// Pre-fill print dialog with a clean filename
document.title = '{{ site.data.resume.name }} Resume';
</script>
</body>
</html>
Creating the resume page
Create resume.md in your project root:
---
layout: resume
permalink: /resume/
sitemap: true
---
Then write the resume content using Liquid to pull from site.data.resume. Because this is a Markdown file, you can mix Liquid and HTML freely:
{% assign r = site.data.resume %}
<header class="resume__header">
<div class="resume__header-main">
<h1 class="resume__name">{{ r.name }}</h1>
<p class="resume__title">{{ r.title }}</p>
</div>
<div class="resume__contact">
<span>{{ r.location }}</span>
<a href="mailto:{{ r.email }}">{{ r.email }}</a>
{% if r.phone %}<span>{{ r.phone }}</span>{% endif %}
<a href="{{ r.website }}">{{ r.website }}</a>
<a href="https://github.com/{{ r.github }}">github.com/{{ r.github }}</a>
<a href="https://linkedin.com/in/{{ r.linkedin }}">linkedin.com/in/{{ r.linkedin }}</a>
</div>
</header>
{% if r.summary %}
<section class="resume__section">
<h2 class="resume__section-title">Summary</h2>
<p>{{ r.summary }}</p>
</section>
{% endif %}
<section class="resume__section">
<h2 class="resume__section-title">Experience</h2>
{% for job in r.experience %}
<div class="resume__entry">
<div class="resume__entry-header">
<div>
<h3 class="resume__entry-title">{{ job.role }}</h3>
<p class="resume__entry-sub">{{ job.company }}{% if job.location %} · {{ job.location }}{% endif %}</p>
</div>
<span class="resume__entry-period">{{ job.period }}</span>
</div>
{% if job.highlights %}
<ul class="resume__bullets">
{% for point in job.highlights %}
<li>{{ point }}</li>
{% endfor %}
</ul>
{% endif %}
</div>
{% endfor %}
</section>
<section class="resume__section">
<h2 class="resume__section-title">Education</h2>
{% for edu in r.education %}
<div class="resume__entry">
<div class="resume__entry-header">
<div>
<h3 class="resume__entry-title">{{ edu.degree }}</h3>
<p class="resume__entry-sub">{{ edu.institution }}</p>
{% if edu.grade %}<p class="resume__entry-sub">{{ edu.grade }}</p>{% endif %}
</div>
<span class="resume__entry-period">{{ edu.period }}</span>
</div>
</div>
{% endfor %}
</section>
<section class="resume__section">
<h2 class="resume__section-title">Skills</h2>
{% for group in r.skills %}
<div class="resume__skills-row">
<strong class="resume__skills-category">{{ group.category }}:</strong>
<span>{{ group.items | join: " · " }}</span>
</div>
{% endfor %}
</section>
{% if r.projects %}
<section class="resume__section">
<h2 class="resume__section-title">Projects</h2>
{% for project in r.projects %}
<div class="resume__entry">
<div class="resume__entry-header">
<div>
<h3 class="resume__entry-title">
<a href="{{ project.url }}" target="_blank" rel="noopener">{{ project.name }}</a>
</h3>
<p class="resume__entry-sub">{{ project.tech | join: " · " }}</p>
</div>
</div>
<p>{{ project.description }}</p>
</div>
{% endfor %}
</section>
{% endif %}
{% if r.certifications %}
<section class="resume__section">
<h2 class="resume__section-title">Certifications</h2>
{% for cert in r.certifications %}
<div class="resume__cert">
<strong>{{ cert.name }}</strong> — {{ cert.issuer }}, {{ cert.year }}
</div>
{% endfor %}
</section>
{% endif %}
<footer class="resume__footer">
<button class="resume__print-btn" onclick="window.print()">Download PDF</button>
<p class="resume__updated">Updated {{ site.time | date: "%B %Y" }}</p>
</footer>
Print-friendly CSS
The print stylesheet is what makes this better than a static PDF — it generates a clean document from your live web page:
/* assets/css/resume.css */
.resume {
max-width: 820px;
margin: 0 auto;
padding: 2rem 2.5rem;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
font-size: 0.875rem;
color: #1a1a1a;
line-height: 1.55;
}
/* Header */
.resume__header { margin-bottom: 1.25rem; }
.resume__name { font-size: 1.75rem; font-weight: 700; margin: 0 0 0.125rem; }
.resume__title { font-size: 1rem; color: #555; margin: 0 0 0.5rem; }
.resume__contact {
display: flex; flex-wrap: wrap; gap: 0.25rem 1rem;
font-size: 0.78rem; color: #555;
}
.resume__contact a { color: #2563eb; text-decoration: none; }
.resume__contact a:hover { text-decoration: underline; }
/* Sections */
.resume__section { margin-bottom: 1.25rem; }
.resume__section-title {
font-size: 0.7rem; font-weight: 700;
text-transform: uppercase; letter-spacing: 0.12em;
color: #666; border-bottom: 1px solid #ddd;
padding-bottom: 0.2rem; margin-bottom: 0.75rem;
}
/* Entries */
.resume__entry { margin-bottom: 0.875rem; }
.resume__entry-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 1rem; }
.resume__entry-title { font-size: 0.9rem; font-weight: 600; margin: 0; }
.resume__entry-title a { color: inherit; text-decoration: none; }
.resume__entry-title a:hover { color: #2563eb; }
.resume__entry-sub { font-size: 0.8rem; color: #555; margin: 0.1rem 0 0; }
.resume__entry-period { font-size: 0.78rem; color: #888; white-space: nowrap; }
.resume__bullets { margin: 0.4rem 0 0 1.1rem; padding: 0; }
.resume__bullets li { margin-bottom: 0.2rem; }
/* Skills */
.resume__skills-row { margin-bottom: 0.3rem; }
.resume__skills-category { margin-right: 0.4rem; }
/* Certifications */
.resume__cert { margin-bottom: 0.3rem; }
/* Footer */
.resume__footer { margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid #eee; display: flex; align-items: center; gap: 1rem; }
.resume__print-btn {
padding: 0.4rem 1rem; background: #2563eb; color: white;
border: none; border-radius: 4px; cursor: pointer; font-size: 0.85rem;
}
.resume__updated { font-size: 0.75rem; color: #999; margin: 0; }
/* Print styles */
@media print {
body { background: white; -webkit-print-color-adjust: exact; }
.resume { max-width: 100%; padding: 0; }
.resume__print-btn { display: none; }
.resume__footer { border: none; padding: 0; margin: 0; }
.resume__updated { display: none; }
/* Show full URLs for links */
.resume__contact a::after {
content: " (" attr(href) ")";
font-size: 0.65rem; color: #888;
}
/* Avoid page breaks inside entries */
.resume__entry { break-inside: avoid; }
.resume__section { break-inside: avoid; }
@page {
margin: 1.5cm 1.8cm;
size: A4;
}
}
To generate a PDF: open the resume in Chrome, press Cmd+P (Mac) or Ctrl+P (Windows), set destination to “Save as PDF”, and save. The print styles handle margins and font sizes automatically.
Hosting on GitHub Pages
Push your Jekyll project to a repository named yourusername.github.io. Enable GitHub Pages in the repository settings, select the main branch as the source, and your resume is live at https://yourusername.github.io/resume/. For a custom domain, add a CNAME file to the repository root containing your domain name and configure your DNS accordingly.
Tips for an effective online resume
Keep it one page for print. Use your browser’s print preview to check that everything fits on one A4 or US Letter page. Recruiters printing resumes expect one page for candidates with fewer than ten years of experience.
Quantify everything. “Reduced load time” is vague. “Reduced median page load from 4.2s to 0.8s” is specific and memorable. Every bullet point that can include a number should include one.
Link to live work. One of the advantages of an online resume over a PDF is that project names can be clickable links. Use them.
Keep the YAML updated continuously. Do not wait until you are job hunting to update your resume. After each project ships, after each certification, after each promotion — spend five minutes in _data/resume.yml. A current resume is far easier to maintain than a stale one you need to reconstruct under pressure.
Multiple CV variants. If you apply for both technical and management roles, maintain separate data files — _data/resume-eng.yml and _data/resume-mgr.yml — and create separate pages that pull from each. The layouts can be identical; only the data changes.
Browse the Resume/CV category on JekyllHub for pre-built Jekyll resume themes that include layouts, print styles, and data structures ready to customise.
Making your Jekyll resume rank in search
A resume hosted as a static Jekyll site is indexable by search engines — which means your name, skills, and job titles can appear in Google results when recruiters and hiring managers search for candidates with your profile. This is a genuine competitive advantage over a PDF resume or a locked-down platform like LinkedIn, where your content is only visible through the platform’s own search.
To maximise search visibility, include your full name prominently on the page in an <h1> tag, use natural language to describe your skills and experience rather than keyword-stuffing, and ensure the page has a proper <title> tag with your name and current role. The jekyll-seo-tag plugin handles the meta tags automatically from your front matter — set title: Your Name | Software Engineer and description: Resume and portfolio of Your Name in the page front matter.
Add schema markup for your resume using the Schema.org Person type. This structured data helps search engines understand the content type and may enable rich results. The resume schemas include jobTitle, worksFor, alumniOf, knowsAbout, and url fields that map directly to a typical resume’s content. Place the schema JSON-LD block in your page layout’s <head> section.
Integrating your resume with your portfolio
A resume and a portfolio serve related but different purposes. The resume is the data layer — facts, dates, skills, titles, measurable outcomes. The portfolio is the narrative layer — what you built, how you approached it, what it looked like, what impact it had. The best developer and designer sites integrate both: a concise resume section (either a separate page or a section on the homepage) linked to rich portfolio case studies that provide the detail and storytelling that a resume format cannot hold.
In Jekyll, this integration is clean. Your resume data lives in _data/resume.yml; your portfolio projects live in _projects/ as a collection. The resume page references the data file; each project page references the project collection. Both are rendered with their respective layouts. Internal links between the resume (“Led development of Project X”) and the case study for Project X (“Here is how we built it”) create a connected experience that keeps visitors engaged longer and demonstrates the full depth of your work.
Maintain both the resume and portfolio simultaneously. A resume with skills and titles but no portfolio leaves hiring managers wondering whether the experience is substantive. A portfolio with impressive project pages but a sparse resume makes it hard to verify employment history and skills at a glance. Together, they form a comprehensive professional profile that answers both the quick-scan questions and the deep-dive ones.
Print-optimised CSS for your Jekyll resume
Many job applications still require a PDF resume. Rather than maintaining a separate document, a well-designed Jekyll resume with print CSS lets you generate a PDF directly from the browser — matching the design and data on your web resume exactly, with no reformatting required.
Print CSS is enabled with the @media print query. Key rules to include: hide navigation, footer, and any interactive elements; set margins to 1 inch on all sides; use black text on white background (ignore any dark mode settings); set font size to 11-12pt; avoid page breaks inside experience blocks using page-break-inside: avoid; and remove all link underlines and colours so URLs are printed as clean text.
Test your print output by opening the browser’s print preview (Cmd+P on macOS) and confirming that the layout is clean, text is readable, and nothing important is cut off at page breaks. The output quality should be sufficient to print directly or to export as a PDF using the browser’s built-in “Save as PDF” option.
If your resume runs to two pages, add a printed page number and your name to the header of the second page using @page :left and @page :right CSS rules — a professional touch that ensures both pages are identifiable if printed and separated.
Hosting and sharing your Jekyll resume site
Your resume site’s URL is part of your professional brand. A clean custom domain (yourname.com or resume.yourname.com) is significantly more professional than a GitHub Pages default URL. Domain registration costs approximately $10-15 per year, and hosting on GitHub Pages or Cloudflare Pages is free. This is the single most cost-effective professional investment available to a job seeker.
Include the URL in your email signature, on your LinkedIn profile, on business cards, and at the top of any PDF resume you submit. When a recruiter receives a PDF resume and can verify it against a live, detailed website with project examples, the impression is substantially more credible than a PDF alone.
Keep the site updated. A resume site that shows a role you left two years ago creates confusion and signals that you are not paying attention to details. Set a calendar reminder to update your site within two weeks of any job change, promotion, completed project, or new skill acquisition. The habit of maintaining your public professional presence is itself a positive signal to anyone evaluating you.