Give feedback as an early user and get the docs plus a full website free. Code FRIEND: valid on one-time purchases only.

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

1

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",
};
2

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 for og:image
  • Without: falls back to /assets/images/logo-small.png
  • Blog posts: reuse the post's image automatically

✅ 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

1

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 %}
2

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: