How to Build a Resume or CV Website with Jekyll
Create a professional online resume or CV with Jekyll β structured front matter, clean layout, PDF export, and free hosting on GitHub Pages.
An online resume or CV is one of the most valuable things a developer, designer, academic, or job seeker can have. A Jekyll-based CV gives you full control over the design, is free to host, and can be printed to PDF from the browser. Here is how to build one.
Why Jekyll for a resume?
- Free to host on GitHub Pages at
yourusername.github.io - Full design control β no template constraints, no watermarks
- Easy to update β change a YAML file and push to GitHub
- Printable β use
@media printCSS to create a clean PDF version - Versioned β Git history tracks every version of your resume
Structuring resume data in Jekyll
The best approach is to store all resume content in _data/resume.yml β a single YAML file you can update without touching any HTML.
# _data/resume.yml
name: "Marcus Webb"
title: "Senior Software Engineer"
email: "marcus@example.com"
website: "https://marcuswebb.io"
github: "marcuswebb"
linkedin: "marcus-webb"
location: "London, UK"
summary: "Senior software engineer with 8 years of experience building scalable web applications. Specialised in Ruby, JavaScript, and developer tooling."
experience:
- company: "Acme Corp"
role: "Senior Software Engineer"
period: "Jan 2022 β Present"
location: "London, UK"
highlights:
- "Led migration of monolith to microservices, reducing deployment time by 60%"
- "Mentored team of 4 junior engineers"
- "Implemented CI/CD pipeline with GitHub Actions"
- company: "Beta Startup"
role: "Software Engineer"
period: "Mar 2019 β Dec 2021"
location: "Remote"
highlights:
- "Built customer-facing API serving 500k requests/day"
- "Reduced page load time from 4.2s to 0.8s through performance optimisation"
education:
- institution: "University of Edinburgh"
degree: "BSc Computer Science"
period: "2015 β 2019"
grade: "First Class Honours"
skills:
- category: "Languages"
items: ["Ruby", "JavaScript", "TypeScript", "Python", "SQL"]
- category: "Frameworks"
items: ["Rails", "React", "Node.js", "Jekyll"]
- category: "Tools"
items: ["Git", "Docker", "AWS", "PostgreSQL", "Redis"]
projects:
- name: "JekyllHub"
url: "https://jekyllhub.com"
description: "A marketplace for Jekyll themes serving 10,000+ monthly visitors."
tech: ["Jekyll", "JavaScript", "SCSS"]
certifications:
- name: "AWS Certified Solutions Architect"
issuer: "Amazon Web Services"
year: 2023
Creating the resume layout
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> β Resume</title>
<link rel="stylesheet" href="/assets/css/resume.css">
</head>
<body>
<main class="resume">
<div class="read-progress" id="read-progress"></div>
<article class="post" itemscope itemtype="https://schema.org/BlogPosting">
<header class="post-hero">
<div class="container">
<div class="post-hero__breadcrumb">
<a href="/">Home</a>
<span class="breadcrumb-sep">βΊ</span>
<a href="/blog/">Blog</a>
<span class="breadcrumb-sep">βΊ</span>
<span>Core Web Vitals for Jekyll Sites: A Practical Optimisation Guide (2026)</span>
</div>
<div class="post-hero__inner">
<span class="post-hero__cat">Tutorial</span>
<h1 class="post-hero__title" itemprop="name">Core Web Vitals for Jekyll Sites: A Practical Optimisation Guide (2026)</h1>
<p class="post-hero__desc" itemprop="description">How to measure and improve Core Web Vitals (LCP, INP, CLS) on your Jekyll site β with practical fixes for the most common issues.</p>
<div class="post-hero__meta">
<span class="post-meta-item">
<svg width="15" height="15" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
Updated <time itemprop="dateModified" datetime="2026-07-19T00:00:00+00:00">July 19, 2026</time>
</span>
<span class="post-meta-item">
<svg width="15" height="15" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
7 min read
</span>
</div>
</div>
</div>
</header>
<div class="post-cover">
<div class="container">
<img src="/assets/images/blog/jekyll-core-web-vitals.webp" alt="Core Web Vitals for Jekyll Sites: A Practical Optimisation Guide (2026)" class="post-cover__img" itemprop="image">
</div>
</div>
<div class="container">
<div class="post-body">
<div class="post-body__main">
<div class="post-toc" id="post-toc" data-collapsed="false" style="display:none">
<button class="post-toc__label" id="toc-toggle" aria-expanded="false" aria-controls="toc-body">
<span class="post-toc__label-left">
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h10"/></svg>
Table of Contents
</span>
<svg class="post-toc__chevron" width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg>
</button>
<nav id="toc-body" class="toc"></nav>
</div>
<div class="prose" itemprop="articleBody">
<p>Jekyll sites start with an inherent performance advantage β no database, no server-side rendering, no PHP. But Core Web Vitals are not just about server speed. Largest Contentful Paint (LCP), Interaction to Next Paint (INP), and Cumulative Layout Shift (CLS) depend on how your HTML, CSS, JavaScript, images, and fonts are delivered to the browser.</p>
<p>Here is how to measure and fix each one.</p>
<h2 id="measuring-core-web-vitals">Measuring Core Web Vitals</h2>
<p>Before optimising, measure your current scores:</p>
<p><strong>Google PageSpeed Insights</strong> β <a href="https://pagespeed.web.dev">pagespeed.web.dev</a> gives you both lab scores and real-world field data (Core Web Vitals from the Chrome User Experience Report).</p>
<p><strong>Chrome DevTools</strong> β Open DevTools β Performance tab β record a page load. Check the LCP marker and layout shift events.</p>
<p><strong>Web Vitals extension</strong> β Install the Google Web Vitals Chrome extension for real-time CWV scores as you browse your site.</p>
<p>Run tests on mobile, not just desktop. Mobile scores are what Google uses for ranking.</p>
<h2 id="largest-contentful-paint-lcp">Largest Contentful Paint (LCP)</h2>
<p>LCP measures how long it takes for the largest visible element to render. The target is under 2.5 seconds. For most Jekyll sites, the LCP element is either a hero image or the largest heading.</p>
<h3 id="fix-1-preload-your-lcp-image">Fix 1: Preload your LCP image</h3>
<p>If your LCP element is a hero image, add a preload hint in <code class="language-plaintext highlighter-rouge"><head></code>:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{% if page.image %}
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"preload"</span> <span class="na">as=</span><span class="s">"image"</span> <span class="na">href=</span><span class="s">"{{ page.image | relative_url }}"</span><span class="nt">></span>
{% endif %}
</code></pre></div></div>
<h3 id="fix-2-use-modern-image-formats">Fix 2: Use modern image formats</h3>
<p>Convert images to WebP (25β35% smaller than JPEG) or AVIF (even smaller):</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cwebp <span class="nt">-q</span> 85 hero.jpg <span class="nt">-o</span> hero.webp
</code></pre></div></div>
<p>Reference with a <code class="language-plaintext highlighter-rouge"><picture></code> element for fallback:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><picture></span>
<span class="nt"><source</span> <span class="na">srcset=</span><span class="s">"/assets/images/hero.avif"</span> <span class="na">type=</span><span class="s">"image/avif"</span><span class="nt">></span>
<span class="nt"><source</span> <span class="na">srcset=</span><span class="s">"/assets/images/hero.webp"</span> <span class="na">type=</span><span class="s">"image/webp"</span><span class="nt">></span>
<span class="nt"><img</span> <span class="na">src=</span><span class="s">"/assets/images/hero.jpg"</span> <span class="na">alt=</span><span class="s">"Hero"</span> <span class="na">width=</span><span class="s">"1200"</span> <span class="na">height=</span><span class="s">"630"</span><span class="nt">></span>
<span class="nt"></picture></span>
</code></pre></div></div>
<h3 id="fix-3-add-width-and-height-to-images">Fix 3: Add width and height to images</h3>
<p>This prevents layout shift and helps the browser calculate space before the image loads:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><img</span> <span class="na">src=</span><span class="s">"hero.webp"</span> <span class="na">alt=</span><span class="s">"Hero"</span> <span class="na">width=</span><span class="s">"1200"</span> <span class="na">height=</span><span class="s">"630"</span> <span class="na">loading=</span><span class="s">"lazy"</span><span class="nt">></span>
</code></pre></div></div>
<p>Do not use <code class="language-plaintext highlighter-rouge">loading="lazy"</code> on above-the-fold images β it delays the LCP element. Use <code class="language-plaintext highlighter-rouge">loading="eager"</code> or omit the attribute for hero images.</p>
<h3 id="fix-4-self-host-your-fonts">Fix 4: Self-host your fonts</h3>
<p>Google Fonts adds a cross-origin request that delays rendering. Self-host fonts instead:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">@font-face</span> <span class="p">{</span>
<span class="nl">font-family</span><span class="p">:</span> <span class="s1">"Inter"</span><span class="p">;</span>
<span class="nl">src</span><span class="p">:</span> <span class="sx">url("/assets/fonts/inter-v13-latin-regular.woff2")</span> <span class="n">format</span><span class="p">(</span><span class="s1">"woff2"</span><span class="p">);</span>
<span class="py">font-display</span><span class="p">:</span> <span class="n">swap</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The <code class="language-plaintext highlighter-rouge">font-display: swap</code> ensures text is visible while the font loads (using a system font fallback), preventing invisible text during load.</p>
<h3 id="fix-5-eliminate-render-blocking-resources">Fix 5: Eliminate render-blocking resources</h3>
<p>CSS in <code class="language-plaintext highlighter-rouge"><head></code> blocks rendering. JavaScript in <code class="language-plaintext highlighter-rouge"><head></code> blocks rendering. Move non-critical CSS to inline critical styles, and add <code class="language-plaintext highlighter-rouge">defer</code> or <code class="language-plaintext highlighter-rouge">async</code> to all scripts:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- Bad --></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/assets/js/main.js"</span><span class="nt">></script></span>
<span class="c"><!-- Good --></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/assets/js/main.js"</span> <span class="na">defer</span><span class="nt">></script></span>
</code></pre></div></div>
<h2 id="interaction-to-next-paint-inp">Interaction to Next Paint (INP)</h2>
<p>INP replaced First Input Delay (FID) in 2024. It measures the time from any user interaction (click, tap, keyboard input) to the next frame painted. The target is under 200ms.</p>
<p>Jekyll sites with minimal JavaScript have excellent INP scores by default. Problems arise when you load heavy JavaScript that blocks the main thread.</p>
<h3 id="fix-1-defer-non-critical-javascript">Fix 1: Defer non-critical JavaScript</h3>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><script </span><span class="na">src=</span><span class="s">"/assets/js/analytics.js"</span> <span class="na">defer</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">src=</span><span class="s">"/assets/js/chat-widget.js"</span> <span class="na">defer</span><span class="nt">></script></span>
</code></pre></div></div>
<h3 id="fix-2-avoid-long-tasks">Fix 2: Avoid long tasks</h3>
<p>Any JavaScript task that runs longer than 50ms can delay interactions. Use the Performance tab in Chrome DevTools to find long tasks (shown as red bars in the main thread timeline).</p>
<p>Break up long loops or calculations using <code class="language-plaintext highlighter-rouge">setTimeout</code>:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Instead of one blocking loop:</span>
<span class="kd">function</span> <span class="nx">processItems</span><span class="p">(</span><span class="nx">items</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">items</span><span class="p">.</span><span class="nx">forEach</span><span class="p">(</span><span class="nx">item</span> <span class="o">=></span> <span class="nx">processItem</span><span class="p">(</span><span class="nx">item</span><span class="p">));</span> <span class="c1">// blocks if items is large</span>
<span class="p">}</span>
<span class="c1">// Break it up:</span>
<span class="kd">function</span> <span class="nx">processItemsAsync</span><span class="p">(</span><span class="nx">items</span><span class="p">,</span> <span class="nx">index</span> <span class="o">=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">processItem</span><span class="p">(</span><span class="nx">items</span><span class="p">[</span><span class="nx">index</span><span class="p">]);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span> <span class="o"><</span> <span class="nx">items</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">setTimeout</span><span class="p">(()</span> <span class="o">=></span> <span class="nx">processItemsAsync</span><span class="p">(</span><span class="nx">items</span><span class="p">,</span> <span class="nx">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="fix-3-remove-unused-javascript">Fix 3: Remove unused JavaScript</h3>
<p>Audit what JavaScript your site loads with the Chrome DevTools Coverage tab (DevTools β More tools β Coverage). Anything with high unused percentage is a candidate for removal or lazy loading.</p>
<h2 id="cumulative-layout-shift-cls">Cumulative Layout Shift (CLS)</h2>
<p>CLS measures unexpected layout movement β elements jumping around as the page loads. The target is under 0.1. Common causes on Jekyll sites:</p>
<h3 id="fix-1-always-specify-image-dimensions">Fix 1: Always specify image dimensions</h3>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- Bad β no dimensions, layout shifts when image loads --></span>
<span class="nt"><img</span> <span class="na">src=</span><span class="s">"hero.webp"</span> <span class="na">alt=</span><span class="s">"Hero"</span><span class="nt">></span>
<span class="c"><!-- Good β browser reserves space --></span>
<span class="nt"><img</span> <span class="na">src=</span><span class="s">"hero.webp"</span> <span class="na">alt=</span><span class="s">"Hero"</span> <span class="na">width=</span><span class="s">"800"</span> <span class="na">height=</span><span class="s">"450"</span><span class="nt">></span>
</code></pre></div></div>
<p>Or use CSS <code class="language-plaintext highlighter-rouge">aspect-ratio</code>:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">.post-image</span> <span class="p">{</span>
<span class="py">aspect-ratio</span><span class="p">:</span> <span class="m">16</span> <span class="p">/</span> <span class="m">9</span><span class="p">;</span>
<span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="fix-2-avoid-inserting-content-above-existing-content">Fix 2: Avoid inserting content above existing content</h3>
<p>Announcement bars, cookie banners, and newsletter popups that appear after page load cause CLS. Either:</p>
<ul>
<li>Include them in the initial HTML (so they are rendered with the page, not injected later)</li>
<li>Reserve space for them with a fixed height placeholder</li>
</ul>
<h3 id="fix-3-use-font-display-swap-and-size-adjust">Fix 3: Use <code class="language-plaintext highlighter-rouge">font-display: swap</code> and size-adjust</h3>
<p>When a custom font loads, it can shift text because metrics differ from the fallback font. Use the <code class="language-plaintext highlighter-rouge">size-adjust</code>, <code class="language-plaintext highlighter-rouge">ascent-override</code>, and <code class="language-plaintext highlighter-rouge">descent-override</code> CSS properties to make your fallback match your custom font:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">@font-face</span> <span class="p">{</span>
<span class="nl">font-family</span><span class="p">:</span> <span class="s1">"Inter-fallback"</span><span class="p">;</span>
<span class="nl">src</span><span class="p">:</span> <span class="n">local</span><span class="p">(</span><span class="s1">"Arial"</span><span class="p">);</span>
<span class="py">size-adjust</span><span class="p">:</span> <span class="m">107%</span><span class="p">;</span>
<span class="py">ascent-override</span><span class="p">:</span> <span class="m">90%</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">body</span> <span class="p">{</span>
<span class="nl">font-family</span><span class="p">:</span> <span class="s1">"Inter"</span><span class="p">,</span> <span class="s1">"Inter-fallback"</span><span class="p">,</span> <span class="nb">sans-serif</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="fix-4-avoid-dynamically-injected-ads-or-embeds">Fix 4: Avoid dynamically injected ads or embeds</h3>
<p>Third-party embeds (Twitter, YouTube, Google Ads) that do not have reserved space cause significant CLS. Use placeholder containers with explicit dimensions:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">style=</span><span class="s">"aspect-ratio: 16/9; background: #f3f4f6;"</span><span class="nt">></span>
<span class="c"><!-- YouTube embed loads here --></span>
<span class="nt"></div></span>
</code></pre></div></div>
<h2 id="jekyll-specific-optimisations">Jekyll-specific optimisations</h2>
<h3 id="inline-critical-css">Inline critical CSS</h3>
<p>Extract the CSS needed to render above-the-fold content and inline it in <code class="language-plaintext highlighter-rouge"><head></code>:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><style></span>
<span class="c">/* Critical CSS β only what is needed for above-fold content */</span>
<span class="nt">body</span> <span class="p">{</span> <span class="nl">margin</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span> <span class="nl">font-family</span><span class="p">:</span> <span class="n">system-ui</span><span class="p">,</span> <span class="nb">sans-serif</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.navbar</span> <span class="p">{</span> <span class="nl">height</span><span class="p">:</span> <span class="m">64px</span><span class="p">;</span> <span class="nl">background</span><span class="p">:</span> <span class="m">#fff</span><span class="p">;</span> <span class="p">}</span>
<span class="nc">.hero</span> <span class="p">{</span> <span class="nl">padding</span><span class="p">:</span> <span class="m">4rem</span> <span class="m">1rem</span><span class="p">;</span> <span class="p">}</span>
<span class="nt"></style></span>
<span class="nt"><link</span> <span class="na">rel=</span><span class="s">"preload"</span> <span class="na">href=</span><span class="s">"/assets/css/main.css"</span> <span class="na">as=</span><span class="s">"style"</span> <span class="na">onload=</span><span class="s">"this.onload=null;this.rel='stylesheet'"</span><span class="nt">></span>
</code></pre></div></div>
<h3 id="compress-jekyll-output-with-a-plugin">Compress Jekyll output with a plugin</h3>
<p>Add <code class="language-plaintext highlighter-rouge">jekyll-compress-html</code> to minify your HTML output:</p>
<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># Gemfile</span>
<span class="n">gem</span> <span class="s2">"jekyll-compress-html"</span>
</code></pre></div></div>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># _config.yml</span>
<span class="na">compress_html</span><span class="pi">:</span>
<span class="na">clippings</span><span class="pi">:</span> <span class="s">all</span>
<span class="na">comments</span><span class="pi">:</span> <span class="s">all</span>
<span class="na">endings</span><span class="pi">:</span> <span class="s">all</span>
</code></pre></div></div>
<h2 id="target-scores">Target scores</h2>
<table>
<thead>
<tr>
<th>Metric</th>
<th>Good</th>
<th>Needs improvement</th>
<th>Poor</th>
</tr>
</thead>
<tbody>
<tr>
<td>LCP</td>
<td>< 2.5s</td>
<td>2.5β4s</td>
<td>> 4s</td>
</tr>
<tr>
<td>INP</td>
<td>< 200ms</td>
<td>200β500ms</td>
<td>> 500ms</td>
</tr>
<tr>
<td>CLS</td>
<td>< 0.1</td>
<td>0.1β0.25</td>
<td>> 0.25</td>
</tr>
</tbody>
</table>
<p>A well-optimised Jekyll site can consistently achieve LCP under 1 second, INP under 50ms, and CLS of 0 β well above Googleβs βgoodβ threshold for all three metrics.</p>
</div>
<div class="post-tags">
</div>
<div class="post-share">
<span class="post-share__label">Share</span>
<a href="https://twitter.com/intent/tweet?url=https%3A%2F%2Fjekyllhub.com%2Ftutorial%2F2026%2F06%2F06%2Fjekyll-core-web-vitals%2F&text=Core+Web+Vitals+for+Jekyll+Sites%3A+A+Practical+Optimisation+Guide+%282026%29" target="_blank" rel="noopener" class="post-share__btn post-share__btn--twitter">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-4.714-6.231-5.401 6.231H2.746l7.73-8.835L1.254 2.25H8.08l4.253 5.622zm-1.161 17.52h1.833L7.084 4.126H5.117z"/></svg>
X / Twitter
</a>
<a href="https://www.linkedin.com/shareArticle?mini=true&url=https%3A%2F%2Fjekyllhub.com%2Ftutorial%2F2026%2F06%2F06%2Fjekyll-core-web-vitals%2F&title=Core+Web+Vitals+for+Jekyll+Sites%3A+A+Practical+Optimisation+Guide+%282026%29" target="_blank" rel="noopener" class="post-share__btn post-share__btn--linkedin">
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>
LinkedIn
</a>
<button class="post-share__btn post-share__btn--copy" onclick="JekyllHub.copyPostLink(this)">
<svg width="14" height="14" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"/></svg>
<span>Copy Link</span>
</button>
</div>
<nav class="post-nav" aria-label="Post navigation">
<div class="post-nav__prev">
<a href="/tutorial/2026/06/05/jekyll-headless-cms/" class="post-nav__link">
<span class="post-nav__label">β Previous</span>
<span class="post-nav__title">Jekyll with a Headless CMS: Contentful, Sanity, and Decap Compared</span>
</a>
</div>
<div class="post-nav__center">
<a href="/blog/" class="btn btn--secondary btn--sm">All Posts</a>
</div>
<div class="post-nav__next">
<a href="/tutorial/2026/06/07/jekyll-resume-cv-website/" class="post-nav__link post-nav__link--next">
<span class="post-nav__label">Next β</span>
<span class="post-nav__title">How to Build a Resume or CV Website with Jekyll</span>
</a>
</div>
</nav>
</div>
<aside class="post-body__sidebar">
<div class="sidebar-card">
<h3 class="sidebar-card__title">Browse Themes</h3>
<ul class="post-sidebar__links">
<li><a href="/jekyll-academic-themes/">π Academic Themes</a></li>
<li><a href="/jekyll-blog-themes/">βοΈ Blog Themes</a></li>
<li><a href="/jekyll-business-themes/">πΌ Business Themes</a></li>
<li><a href="/jekyll-documentation-themes/">π Documentation Themes</a></li>
<li><a href="/jekyll-e-commerce-themes/">π E-commerce Themes</a></li>
<li><a href="/jekyll-landing-page-themes/">π Landing Page Themes</a></li>
<li><a href="/jekyll-personal-themes/">π€ Personal Themes</a></li>
<li><a href="/jekyll-portfolio-themes/">π¨ Portfolio Themes</a></li>
<li><a href="/jekyll-resume-cv-themes/">π Resume/CV Themes</a></li>
<li><a href="/jekyll-github-pages-themes/"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor" style="display:inline;vertical-align:middle;margin-right:4px"><path d="M12 0C5.374 0 0 5.373 0 12c0 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.107-.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.23A11.509 11.509 0 0 1 12 5.803c1.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.911 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.576C20.566 21.797 24 17.3 24 12c0-6.627-5.373-12-12-12z"/></svg>GitHub Pages Themes</a></li>
</ul>
<a href="/themes/" class="btn btn--primary btn--full" style="margin-top:var(--space-5)">Browse All Themes β</a>
</div>
<div class="sidebar-card" style="margin-top:var(--space-6)">
<h3 class="sidebar-card__title">Submit Your Theme</h3>
<p style="font-size:0.875rem;color:var(--text-3);line-height:1.6;margin-bottom:var(--space-4)">Built a Jekyll theme? Share it with thousands of developers.</p>
<a href="/submit/" class="btn btn--secondary btn--full">Submit a Theme β</a>
</div>
</aside>
</div>
</div>
<!-- Related Themes β rendered by JS from SITE_DATA, shuffled per page load -->
<section class="post-related-themes" style="display:none">
<div class="container">
<h2 class="post-related-themes__title">Themes You Might Like</h2>
<div class="themes-grid themes-grid--4" id="post-related-themes-grid"
data-tags="[]"
data-related-category="Blog"></div>
</div>
</section>
</article>
<!-- Back to top -->
<button class="back-to-top" id="back-to-top" aria-label="Back to top" onclick="window.scrollTo({top:0,behavior:'smooth'})">
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 15l7-7 7 7"/>
</svg>
</button>
<script src="/assets/js/post.js" defer></script>
</main>
</body>
</html>
Creating the resume page
Create resume.md (or cv.md) in your project root:
---
layout: resume
permalink: /resume/
---
{% assign r = site.data.resume %}
<header class="resume__header">
<h1 class="resume__name">{{ r.name }}</h1>
<p class="resume__title">{{ r.title }}</p>
<div class="resume__contact">
<span>{{ r.location }}</span>
<a href="mailto:{{ r.email }}">{{ r.email }}</a>
<a href="https://github.com/{{ r.github }}">GitHub</a>
<a href="https://linkedin.com/in/{{ r.linkedin }}">LinkedIn</a>
<a href="{{ r.website }}">{{ r.website }}</a>
</div>
</header>
<section class="resume__section">
<h2>Summary</h2>
<p>{{ r.summary }}</p>
</section>
<section class="resume__section">
<h2>Experience</h2>
{% for job in r.experience %}
<div class="resume__job">
<div class="resume__job-header">
<div>
<h3>{{ job.role }}</h3>
<p class="resume__company">{{ job.company }} Β· {{ job.location }}</p>
</div>
<span class="resume__period">{{ job.period }}</span>
</div>
<ul>
{% for highlight in job.highlights %}
<li>{{ highlight }}</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</section>
<section class="resume__section">
<h2>Education</h2>
{% for edu in r.education %}
<div class="resume__job">
<div class="resume__job-header">
<div>
<h3>{{ edu.degree }}</h3>
<p class="resume__company">{{ edu.institution }}</p>
</div>
<span class="resume__period">{{ edu.period }}</span>
</div>
{% if edu.grade %}<p>{{ edu.grade }}</p>{% endif %}
</div>
{% endfor %}
</section>
<section class="resume__section">
<h2>Skills</h2>
{% for skill_group in r.skills %}
<div class="resume__skills">
<strong>{{ skill_group.category }}:</strong>
{{ skill_group.items | join: " Β· " }}
</div>
{% endfor %}
</section>
<section class="resume__section">
<h2>Projects</h2>
{% for project in r.projects %}
<div class="resume__project">
<h3><a href="{{ project.url }}">{{ project.name }}</a></h3>
<p>{{ project.description }}</p>
<p class="resume__tech">{{ project.tech | join: " Β· " }}</p>
</div>
{% endfor %}
</section>
Print-friendly CSS
Add print styles to make the resume look great when saved as PDF (File β Print β Save as PDF in Chrome):
/* assets/css/resume.css */
.resume {
max-width: 800px;
margin: 0 auto;
padding: 2rem;
font-family: system-ui, -apple-system, sans-serif;
font-size: 0.9rem;
color: #1a1a1a;
line-height: 1.5;
}
.resume__header { margin-bottom: 1.5rem; }
.resume__name { font-size: 1.75rem; font-weight: 700; margin: 0; }
.resume__title { color: #555; margin: 0.25rem 0; }
.resume__contact { display: flex; gap: 1rem; flex-wrap: wrap; font-size: 0.8rem; margin-top: 0.5rem; }
.resume__contact a { color: #2563eb; text-decoration: none; }
.resume__section { margin-bottom: 1.5rem; }
.resume__section h2 { font-size: 0.85rem; text-transform: uppercase; letter-spacing: 0.1em; color: #555; border-bottom: 1px solid #e5e7eb; padding-bottom: 0.25rem; margin-bottom: 0.75rem; }
.resume__job { margin-bottom: 1rem; }
.resume__job-header { display: flex; justify-content: space-between; align-items: flex-start; }
.resume__job h3 { margin: 0; font-size: 1rem; }
.resume__company { color: #555; margin: 0.1rem 0; font-size: 0.85rem; }
.resume__period { font-size: 0.8rem; color: #888; white-space: nowrap; }
.resume__job ul { margin: 0.5rem 0 0 1.2rem; padding: 0; }
.resume__job li { margin-bottom: 0.25rem; }
.resume__skills { margin-bottom: 0.4rem; }
.resume__tech { font-size: 0.8rem; color: #555; }
@media print {
body { background: white; }
.resume { max-width: 100%; padding: 0; }
a { color: inherit !important; text-decoration: none !important; }
.resume__contact a::after { content: " (" attr(href) ")"; font-size: 0.7rem; }
@page { margin: 1.5cm; }
}
Hosting on GitHub Pages
Push your Jekyll resume to a GitHub repository named yourusername.github.io. GitHub Pages automatically builds and deploys it. Your resume is live at https://yourusername.github.io/resume/ β free, forever, with a custom domain if you want one.
Jekyll resume themes
Rather than building from scratch, you can use an existing Jekyll resume theme:
- Researcher β academic-focused single-page theme
- Online CV β clean, single-page resume layout
- al-folio β popular academic portfolio with publications support
Browse the Resume/CV category on JekyllHub for curated options.
Tips for an effective online resume
Keep it to one page for print. Use the print CSS to verify everything fits on one A4 or Letter page when printed.
Use plain language. Write bullet points that start with action verbs: βBuiltβ, βLedβ, βReducedβ, βImplementedβ. Quantify results where possible.
Keep the YAML updated. The advantage of data-driven resumes is that a single file change updates every page. Update _data/resume.yml after each role, not just before applying.
Add a PDF download link. Include a button that opens the print dialog:
<button onclick="window.print()">Download PDF</button>