McGarrah Technical Blog

Your Jekyll Theme Is Probably Missing head and body Tags

· 7 min read

I spent months debugging why Google AdSense kept rejecting my site. The content was good. The SEO was solid. The privacy policy was in place. Then I looked at the actual HTML my Jekyll theme was generating and found this:

<!DOCTYPE html>
<html lang="en">
<!-- SEO tags here -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="/assets/css/index.css">
<script async src="https://pagead2.googlesyndication.com/..."></script>
<!-- sidebar HTML here -->
<header>...</header>
<article>...</article>
<footer>...</footer>
</html>

No <head>. No </head>. No <body>. No </body>. Everything — meta tags, stylesheets, scripts, navigation, content, footer — dumped directly under <html> with no structural separation.

And it worked perfectly in every browser I tested.

Why Browsers Don’t Care

The HTML5 parsing specification is extraordinarily forgiving. When a browser encounters a <meta> tag without a preceding <head>, it implicitly creates one. When it hits a <header> or <div> without a <body>, it creates that too. The browser’s parser reconstructs the DOM tree you meant to write, even if you didn’t write it.

This is by design. The web is full of broken HTML, and browsers evolved to handle it gracefully. You can view-source on a page with no <head> tag, then inspect the DOM in DevTools, and you’ll see <head> and <body> right where they should be. The browser added them for you.

Why Bots Do Care

Bots and verification systems don’t always use a full HTML5 parser. Many of them:

Google’s AdSense verification bot is one of these. It crawls your page looking for the AdSense script tag inside <head>. If there’s no <head> tag in the raw HTML, the bot can’t confirm the script is installed — even if a browser would render it correctly.

How to Check Your Theme

Open any page on your Jekyll site and view the source (not the DOM inspector — the actual source):

curl -s https://yoursite.com | head -20

You should see something like:

<!DOCTYPE html>
<html lang="en">
<head>
<!-- meta tags, stylesheets, scripts -->
</head>
<body>
<!-- visible content -->
</body>
</html>

If you see meta tags and stylesheets immediately after <html> with no <head> tag, your theme has this problem.

For a quick check of your default layout:

grep -c '<head>' _layouts/default.html
grep -c '<body>' _layouts/default.html

Both should return 1. If either returns 0, keep reading.

The Problems This Causes

1. Bot Verification Failures

Any service that needs to verify a script tag in your <head> will fail:

2. Duplicate Canonical Tags

This often comes bundled with the missing <head> problem. If your theme uses jekyll-seo-tag (which emits a canonical link) and has a manual canonical link, you get two:

<!-- From jekyll-seo-tag -->
<link rel="canonical" href="https://yoursite.com/page/" />
<!-- From default.html -->
<link rel="canonical" href="https://yoursite.com/page/" />

Google explicitly warns against duplicate canonical tags. Without a <head> boundary, it’s easy to miss that both are present.

3. Stylesheets in the Body

Some themes include components that mix <link> stylesheet tags with HTML content. In my case, the sidebar include had:

<link rel="stylesheet" href="/assets/css/sidebar.css">
<aside>
  <nav>...</nav>
</aside>

Without <head>/<body> separation, that <link> tag ends up in the body. Browsers handle it, but it’s invalid HTML and can cause a flash of unstyled content.

The Five-Minute Fix

Here’s the pattern. Your _layouts/default.html should look like this:

<!DOCTYPE html>
<html lang="{{ page.lang | default: site.lang | default: 'en' }}">
<head>
{% seo %}
<meta charset="{{ site.encoding }}">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- stylesheets, scripts, feeds -->
</head>

<body>
<!-- sidebar, navigation, content, footer -->
{{ content }}
</body>
</html>

The rules are simple:

Your Jekyll Theme Is Probably Missing head and body Tags | McGarrah Technical Blog

`, stylesheets, scripts, feed links, analytics, verification scripts

What to Move

If your theme has includes that mix head and body content, split them. My sidebar include had a <link> tag that needed to move to <head>:

<!-- Before: sidebar.html mixed <link> with <aside> -->
<link rel="stylesheet" href="/assets/css/sidebar.css">
<aside>...</aside>

<!-- After: <link> moved to default.html <head>, sidebar.html is just: -->
<aside>...</aside>

And in default.html:

<head>
<!-- other head content -->
{% if site.show_sidebar %}<link rel="stylesheet" href="/assets/css/sidebar.css">{% endif %}
</head>

<body>
{% if site.show_sidebar %}{% include sidebar.html %}{% endif %}
<!-- rest of body -->
</body>

Verify After Fixing

# Build and check
bundle exec jekyll build

# Count tags (should all be 1)
grep -c '<head>'  _site/index.html
grep -c '</head>' _site/index.html
grep -c '<body>'  _site/index.html
grep -c '</body>' _site/index.html

# Check for duplicate canonicals (should be 1)
grep -c 'rel="canonical"' _site/index.html

Which Themes Have This Problem?

I can’t name every theme, but the pattern is common in:

If your theme’s default.html is under 50 lines and doesn’t contain <head> or <body>, it almost certainly has this problem.

My Specific Case

My blog uses a minimal Jekyll theme that I’ve customized extensively over the years. The original theme never had <head> or <body> tags — it relied on browser forgiveness. This worked fine for years until I tried to get AdSense approval and Google’s bot couldn’t find the verification script.

The fix took five minutes. The debugging took months. I wrote about the full AdSense journey in:

But the HTML structure fix was the foundation that made everything else work.

The Takeaway

If you’re running a Jekyll site and anything involving bot verification isn’t working — AdSense, Search Console HTML verification, social media previews — check your <head> and <body> tags before debugging anything else. Browsers will forgive you. Bots won’t.


About the Author: Michael McGarrah is a Cloud Architect with 25+ years in enterprise infrastructure, machine learning, and system administration. He holds an M.S. in Computer Science (AI/ML) from Georgia Tech and a B.S. in Computer Science from NC State University, and is currently pursuing an Executive MBA at UNC Wilmington. LinkedIn · GitHub · ORCID · Google Scholar · Resume