SEO & Metadata
SEO data is centralized in src/_data/client.js, a single source of truth for your business details, domain, and social links. The base layout's <head> builds a complete set of meta and Open Graph tags automatically from client.js and each page's front matter.
The Base Layout <head>
src/_includes/layouts/base.html builds your SEO tags dynamically from client.js and page front matter
Configure Your Site Data
Open src/_data/client.js and fill in your business details. Because it lives in _data, the client object is available in every template.
// src/_data/client.js
module.exports = {
name: "Eleventy Starter Template",
email: "help@eleventystarter.app",
phoneForTel: "555-557-6614",
phoneFormatted: "(555) 557-6614",
address: {
lineOne: "First Address Line",
lineTwo: "Second Address Line",
city: "Springfield",
state: "OH",
zip: "12345",
country: "US",
mapLink: "https://maps.app.goo.gl/TEdS5KoLC9ZcULuQ6",
},
socials: {
facebook: "https://www.facebook.com/",
instagram: "https://www.instagram.com/",
},
//! Include the protocol (https://) and NO trailing slash
domain: "https://www.example.com",
// Available in templates as client.isProduction
isProduction: process.env.ELEVENTY_ENV === "PROD",
};How the <head> Uses It
base.html already wires up description, canonical, and Open Graph tags. Page values (title, description, image) come from front matter; site values come from client.
<!-- src/_includes/layouts/base.html -->
<head>
<!-- Standard meta tags -->
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="{{ description }}">
<link rel="canonical" href="{{ client.domain }}{{ page.url }}">
<meta name="generator" content="{{ eleventy.generator }}">
<!-- Social Media Display -->
<meta property="og:title" content="{{ title }}"/>
<meta property="og:description" content="{{ description }}"/>
<meta property="og:type" content="website"/>
<meta property="og:url" content="{{ client.domain }}{{ page.url }}"/>
<meta property="og:image" content="{{ image or "/assets/images/logo-small.png" }}"/>
<meta property="og:image:secure_url" content="{{ image or "/assets/images/logo-small.png" }}"/>
<!-- Favicons (https://realfavicongenerator.net/) -->
<link rel="icon" type="image/png" href="/assets/favicons/favicon-96x96.png" sizes="96x96"/>
<link rel="icon" type="image/svg+xml" href="/assets/favicons/favicon.svg"/>
<link rel="shortcut icon" href="/assets/favicons/favicon.ico"/>
<link rel="apple-touch-icon" sizes="180x180" href="/assets/favicons/apple-touch-icon.png"/>
<meta name="apple-mobile-web-app-title" content="Starter Kit"/>
<link rel="manifest" href="/assets/favicons/site.webmanifest"/>
<!-- Preloads -->
<link rel="preload" as="image" href="/assets/svgs/logo-black.svg">
<link rel="preload" as="font" type="font/woff2" href="/assets/fonts/roboto-v29-latin-regular.woff2" crossorigin>
<link rel="preload" as="font" type="font/woff2" href="/assets/fonts/roboto-v29-latin-700.woff2" crossorigin>
<!-- Sitewide Stylesheets and Scripts -->
<link rel="stylesheet" href="/assets/css/root.css">
<script defer src="/assets/js/dark.js"></script>
<script defer src="/assets/js/nav.js"></script>
{% block head %}{% endblock %}
<title>{{ title }}</title>
</head>Built-in Automatically
- • Meta description & canonical URL
- • Open Graph (title, description, type, url, image)
- • Favicons & web manifest
- • A
{% block head %}slot for per-page tags
Social Image Strategy
- • With
image: uses that path forog:image - • Without: falls back to
/assets/images/logo-small.png - • Blog posts: reuse the post's
imageautomatically
✅ No Per-Page Wiring Needed
Every page that extends base.html inherits these tags. You only set title, description, and an optional image in front matter.
Per-Page SEO with Front Matter
Set a page's title, description, and social image right in its front matter
Basic Page
Set title and description, extend the base layout, and add any page-specific tags in {% block head %}.
---
title: "About Us | Eleventy Starter Template"
description: "Learn more about us: our mission, values and team."
permalink: "/about/"
---
{% extends "layouts/base.html" %}
{% block head %}
<link rel="stylesheet" href="/assets/css/about.css">
{% endblock %}
{% block body %}
<!-- Page content -->
{% endblock %}Page With a Social Image
Add an image key (an absolute path under /assets) and the base layout uses it for og:image instead of the fallback.
---
title: "Our Work | Eleventy Starter Template"
description: "Browse recent projects and case studies."
permalink: "/portfolio/"
# Used for og:image and og:image:secure_url (1200x630 recommended)
image: "/assets/images/portfolio/port1.jpg"
---
{% extends "layouts/base.html" %}
{% block body %}
<!-- Page content -->
{% endblock %}💡 Recommended for Every Page
- •
title: unique page title (50–60 chars) - •
description: page description (150–160 chars) - •
permalink: clean URL path, e.g./about/ - •
image: custom social preview (1200×630px)
Structured Data (JSON-LD)
Two ready-made schemas ship with the starter as opt-in includes
Schemas Are Opt-In
To keep validation clean, the schema includes are commented out by default in index.html and layouts/post.html. Uncomment them once you've filled in your details, then verify the output.
1. Enable the LocalBusiness Schema (Home)
src/_includes/components/home-schema.html builds a LocalBusiness object from client.js (name, phone, email, address, socials). Uncomment its include in index.html:
{# src/index.html, inside {% block head %} #}
<!-- Uncomment to enable Structured Data, then validate after you deploy:
https://developers.google.com/search/docs/appearance/structured-data -->
<!-- {% include "components/home-schema.html" %} -->What the LocalBusiness Schema Emits
Everything is pulled from client: edit your business details once and the schema follows:
<!-- src/_includes/components/home-schema.html -->
<script type="application/ld+json">
{
"@context": "http://schema.org",
"@type": "LocalBusiness",
"name": "{{ client.name }}",
{% if preloadImg %}"image": "{{ client.domain }}{{ preloadImg }}",{% endif %}
{% if client.phoneFormatted %}"telephone": "{{ client.phoneFormatted }}",{% endif %}
{% if client.email %}"email": "{{ client.email }}",{% endif %}
{% if client.address %}
"address": {
"@type": "PostalAddress"
{%- if client.address.lineOne -%},
"streetAddress": "{{ client.address.lineOne }}{% if client.address.lineTwo %}, {{ client.address.lineTwo }}{% endif %}"
{%- endif -%}
{%- if client.address.city -%},
"addressLocality": "{{ client.address.city }}"
{%- endif -%}
{%- if client.address.state -%},
"addressRegion": "{{ client.address.state }}"
{%- endif -%}
{%- if client.address.zip -%},
"postalCode": "{{ client.address.zip }}"
{%- endif -%}
{%- if client.address.country -%},
"addressCountry": "{{ client.address.country }}"
{%- endif -%}
},
{% endif %}
{% if client.domain and page.url %}"url": "{{ client.domain }}{{ page.url }}",{% endif %}
{% if client.socials %}
"sameAs": [{%- for platform, url in client.socials -%}{% if not loop.first %},{% endif %}"{{ url }}"{%- endfor -%}]
{% endif %}
}
</script>2. Enable the Article Schema (Blog Posts)
components/post-schema.html builds an Article object from the post's front matter (title, description, image, date, author). Uncomment its include in layouts/post.html:
{# src/_includes/layouts/post.html, inside {% block head %} #}
<!-- Uncomment to enable Article Structured Data, then validate after you deploy -->
<!-- {% include "components/post-schema.html" %} -->What the Article Schema Emits
The include builds a full Article object, headline, description, a 1200×630 image, published/modified dates (via the isoDate filter), the author, and a publisher Organization built from client:
<!-- src/_includes/components/post-schema.html -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"mainEntityOfPage": {
"@type": "WebPage",
"@id": "{{ client.domain }}{{ page.url }}"
},
{% if title %}"headline": "{{ title | escape }}",{% endif %}
{% if description %}"description": "{{ description | escape }}",{% endif %}
{% if image %}
"image": {
"@type": "ImageObject",
"url": "{{ client.domain }}{{ image }}",
"width": 1200,
"height": 630
},
{% endif %}
{% if date %}"datePublished": "{{ date | isoDate }}",{% endif %}
{% if date %}"dateModified": "{{ date | isoDate }}",{% endif %}
{% if author %}
"author": {
"@type": "Person",
"name": "{{ author | escape }}"
},
{% endif %}
{% if client.name %}
"publisher": {
"@type": "Organization",
"name": "{{ client.name | escape }}",
"logo": {
"@type": "ImageObject",
"url": "{{ client.domain }}/assets/svgs/logo-black.svg",
"width": 210,
"height": 28
}
}
{% endif %}
}
</script>🎯 Rich Snippets Benefits
Structured data can surface enhanced features in search results. After enabling a schema and deploying, validate the output with Google's Rich Results Test.
Sitemap & Robots.txt
Both are generated for you from client.domain
Sitemap Plugin (already configured)
The starter uses @quasibit/eleventy-plugin-sitemap, registered in .eleventy.js. Its hostname comes from src/config/plugins/sitemap.js:
// src/config/plugins/sitemap.js
const client = require("../../_data/client");
module.exports = {
sitemap: {
hostname: client.domain,
},
};Sitemap Template
src/sitemap.html outputs /sitemap.xml from every page tagged sitemap (a page opts in with tags: "sitemap" in its front matter, like the homepage):
---
permalink: /sitemap.xml
layout: null
eleventyExcludeFromCollections: true
---
{% sitemap collections.sitemap %}Robots.txt
src/robots.html renders /robots.txt and points crawlers at your sitemap:
---
permalink: /robots.txt
layout: null
eleventyExcludeFromCollections: true
---
User-agent: *
Disallow: /admin/
Allow: /
Sitemap: {{ client.domain }}/sitemap.xml📍 Submit Your Sitemap
After deploying, submit /sitemap.xml to search engines:
