How Quesby Handles SEO: Automatic Metadata, JSON-LD, Open Graph

How Quesby Handles SEO: Automatic Metadata, JSON-LD, Open Graph

This article walks through how Quesby handles SEO for you and what that means if you're a developer looking for a lightweight, independent alternative to Astro-style stacks.

Modern meta-frameworks like Astro, Next.js, and similar tools often ship with opinionated SEO helpers, React components, or integrations that live in your runtime. Quesby takes a different approach: it keeps SEO completely static, framework-agnostic, and HTML-first, while still giving you automatic metadata, Open Graph tags, Twitter Cards, and JSON-LD structured data out of the box.

This article walks through how the Quesby core (@quesby/core) handles SEO for you, how it integrates with Eleventy, and what that means if you're a developer looking for a lightweight, independent alternative to Astro-style stacks.


The Core Idea: One SEO Model, Built at Build Time

Quesby is built around a small but powerful idea:

Take site-wide config + page frontmatter → normalize into a single SEO model → render everything (meta tags, Open Graph, JSON-LD) from there at build time.

In the Quesby Boilerplate's base layout (src/_includes/layouts/base.njk), you can see this explicitly:

{# Collect frontmatter variables from template context #}
{%- set pageData = {
seoTitle: seoTitle,
postTitle: postTitle,
title: title,
description: description,
postDescription: postDescription,
image: image,
postImage: postImage,
ogImageAlt: ogImageAlt,
noindex: noindex,
postType: postType,
schemaType: schemaType,
author: author,
date: date,
lastUpdated: lastUpdated,
tags: tags,
seoDisableCoreHead: seoDisableCoreHead,
seoDisableCoreJsonLd: seoDisableCoreJsonLd
} -%}
{%- set seoModel = page | seoModel(site, pageData) -%}
{{ seoModel | seoHeadHtml(site) | safe }}
{{ seoModel | seoJsonLd(site) | safe }}

A few important consequences:

  • Your templates stay clean. You work with regular Eleventy frontmatter; the SEO plumbing is centralized in @quesby/core.
  • Everything is build-time. The seoModel, seoHeadHtml, and seoJsonLd filters do their job during the Eleventy build. No runtime JavaScript, no hydration.
  • You get consistent output. Because everything flows through a single model, titles, descriptions, Open Graph tags, and JSON-LD all share the same source of truth.

Site-Level Configuration: site.json as the Source of Truth

Quesby keeps global SEO defaults in a single data file: src/_data/site.json.

A typical snippet looks like this:

{
"name": "Quesby",
"url": "https://quesby.dev",
"description": "A modern Eleventy boilerplate with Decap CMS, built with content creators in mind",
"author": "Green Panda Studio",
"socialImage": "/assets/images/og-image.jpg",
"twitter": "@quesby",
"language": "en-US"
}

From this, the SEO system derives:

  • Site name & base URL – used for titles, canonical URLs, and Open Graph tags.
  • Default description – used when a page doesn't provide its own description.
  • Default social image – used when a page doesn't specify an image field.
  • Twitter handle & language – wired into Twitter Cards and Open Graph locale.

As a developer, this means you configure your brand-level SEO once, centrally, without sprinkling constants across templates.


Frontmatter: Minimal Fields, Smart Fallbacks

Per-page SEO lives in frontmatter. Quesby's SEO system supports fields like:

  • seoTitle – explicit SEO title (for the <title> tag and social sharing).
  • title / postTitle – content title; used when seoTitle isn't set.
  • description / postDescription – page description.
  • image / postImage – social sharing image.
  • postType – Open Graph type (e.g., "article", "website").
  • noindex – whether a page should be excluded from indexing.
  • schemaType – hints for JSON-LD type when needed (e.g., blog post vs. generic page).
  • author, date, lastUpdated, tags – used to enrich structured data.

The system then applies fallback rules, for example:

  • Title: seoTitle → postTitle → title → site.name
  • Description: description → postDescription → site.description
  • Image: image → postImage → site.socialImage

So a "basic" page can stay very minimal:

---
title: "My Page Title"
description: "Short, human-readable summary for this page."
---

And a more tuned blog post can opt into richer SEO:

---
title: "How to Build Amazing Websites"
description: "Learn the secrets of modern web development."
seoTitle: "Complete Guide: Building Amazing Websites in 2024"
image: "/assets/images/amazing-websites.jpg"
postType: "article"
author: "Your Name"
date: "2024-01-15"
---

You get fine-grained control when you need it, but the defaults are enough for a lot of content.


Automatic Head Metadata: Titles, Descriptions, Canonical, Robots

Inside @quesby/core, Quesby registers a set of Eleventy filters dedicated to SEO:

src/eleventy/seo.js
eleventyConfig.addFilter("canonical", (pageUrl, siteUrl) => {
return absoluteUrl(pageUrl, siteUrl);
});
eleventyConfig.addFilter("seoTitle", (title, siteName) => {
if (!title) return siteName;
return `${title} | ${siteName}`;
});
eleventyConfig.addFilter("absoluteUrl", (url, siteUrl) => {
return absoluteUrl(url, siteUrl);
});
eleventyConfig.addFilter("seoDescription", (description, fallback) => {
return description || fallback || "";
});

On top of this, the seoHeadHtml filter (used in base.njk) renders a complete <head> block for you. The documentation guarantees that the system automatically outputs at least:

  • Basic SEO

    • <title> – using the seoTitletitlesite.name chain.
    • <meta name="description"> – with description fallback to site.description.
    • <link rel="canonical"> – via the canonical filter.
    • <meta name="robots">noindex handling when you opt out of indexing.
  • Open Graph (for social sharing)

    • og:locale – based on site.language.
    • og:type – e.g., "website" or "article" via postType.
    • og:site_name – from site.name.
    • og:title, og:description, og:url.
    • og:image (+ dimensions and alt text when configured).
  • Twitter Cards

    • twitter:card – Quesby uses summary_large_image by default.
    • twitter:site – from site.twitter when present.
    • twitter:title, twitter:description, twitter:image.

The important part for you as a developer: you don't hand-craft these tags. You set a handful of semantic fields; Quesby takes care of assembling valid, consistent metadata.


Structured Data: JSON-LD for Real-World Content

Beyond <meta> tags, Quesby also emits JSON-LD structured data.

In the base layout you see:

<!-- JSON-LD structured data -->
{{ seoModel | seoJsonLd(site) | safe }}

The JSON-LD payload is generated from the same SEO model:

  • Blog posts are described as BlogPosting (from schema.org), with fields like:

    • headline
    • description
    • image
    • author
    • publisher (derived from site.name and logo)
    • datePublished and dateModified (from date / lastUpdated)
  • The site itself is represented as a WebSite object:

    • name and url from site.json
    • description from site.description
    • publisher with Organization information

Because JSON-LD is driven by the same site + frontmatter inputs, you don't have to repeat yourself or maintain a separate schema config. The same update that improves your Open Graph preview will also improve your structured data.

Again, everything is rendered statically into <script type="application/ld+json"> blocks at build time—no client code required.


Sitemaps, Robots, and noindex Integration

SEO in Quesby isn't limited to the <head>. The core also wires SEO concerns into Eleventy collections and utility pages.

  • Sitemap generation
    src/sitemap.njk uses the sitemap collection:

    {% set pages = collections.sitemap | sort(attribute="url") %}
    {% for page in pages %}
    <url>
    <loc>{{ site.url }}{{ page.url }}</loc>
    <lastmod>{{ (page.data.lastmod or page.data.gitDate or page.date) | w3cDate }}</lastmod>
    <changefreq>{{ page.data.changefreq or "monthly" }}</changefreq>
    <priority>{{ page.data.priority or "0.5" }}</priority>
    </url>
    {% endfor %}

    The collection itself is defined in @quesby/core and automatically excludes pages that should not appear in the sitemap:

    • noindex: true
    • eleventyExcludeFromCollections: true
    • 404 pages, admin, XML/JSON utilities, etc.
  • Robots.txt
    src/robots.txt.njk is generated from templates and links directly to the sitemap:

    User-agent: *
    Allow: /
    Sitemap: {{ site.url }}/sitemap.xml

These pieces ensure that the signals you send via noindex and your page structure are consistently reflected in both your metadata and your crawlable endpoints.


Opting Out: When You Need Custom SEO

Even though the SEO system is opinionated, Quesby does not lock you in.

The base layout supports a couple of escape hatches that are passed into the seoModel as part of pageData:

  • seoDisableCoreHead – lets you turn off the automatic head tags for a specific page.
  • seoDisableCoreJsonLd – lets you turn off automatic JSON-LD generation.

That means you can:

  • Keep the global system for 95% of your site.
  • For special landing pages or experimental layouts, disable core SEO and write custom tags manually (or integrate a different generator) without fighting the framework.

You still benefit from the rest of Quesby's structure (content model, collections, sitemap, robots) while taking full control over those specific pages.


How This Compares to Astro-Style Approaches

If you've worked with Astro or other meta-frameworks, you're used to:

  • SEO helpers as framework components (React/Vue/Svelte),
  • complex plugin ecosystems,
  • and often a mix of build-time + runtime concerns.

Quesby's design is deliberately different:

  • No runtime SEO code. Everything is Eleventy + Nunjucks filters, rendered to static HTML.
  • Framework-agnostic output. You get plain HTML and JSON-LD, not a component abstraction.
  • Small surface area. The core exposes a few filters (canonical, seoTitle, absoluteUrl, seoDescription) and the higher-level seoModel/seoHeadHtml/seoJsonLd helpers. That's it.
  • Predictable behavior. Because the SEO system is just part of the Eleventy build pipeline, it behaves like the rest of your static site generation.

If you're comfortable with Eleventy and want SEO that feels like part of your content pipeline, not part of your runtime UI framework, Quesby's approach is very likely aligned with what you want.


Why This Matters for Developer Experience

For someone at your level—already thinking about performance, content structure, and long-term maintainability—the benefits are:

  • Less boilerplate per page. You rarely write <meta> tags by hand.
  • Fewer moving parts. No SEO React components, no client bundles, no extra hydration.
  • Centralized configuration. site.json and a handful of frontmatter fields drive titles, Open Graph, Twitter, JSON-LD, sitemap, and robots together.
  • Static and inspectable. View source and you see exactly what search engines see—no surprises hidden behind client code.

Quesby's SEO system is not trying to be a "magic box". It's a thin, explicit layer on top of Eleventy that bakes best practices into the build, while giving you escape hatches when you need them.


Wrapping Up

Quesby treats SEO as a first-class concern, but implements it in the simplest way possible: static Eleventy filters and templates, wired through a single SEO model. You get automatic metadata, Open Graph tags, Twitter Cards, structured data via JSON-LD, and sitemap/robots integration—without pulling in a heavy framework or runtime layer.

If you're looking for a lightweight, independent alternative to Astro-style stacks—and you like the idea of HTML-first, privacy-first static sites—Quesby's SEO system is designed to give you strong defaults with very little noise.