Martin Duggan Designer & Visual Artist
Web Tech Notes

Content model for this site

A content-model setup for a growing Eleventy site that needs blog content, store products, reusable navigation, related content, and search-ready indexing.

If the site is going to grow into blog content, static pages, a store, and possibly a portfolio, the main shift is to stop thinking in terms of page-specific JSON files and start thinking in terms of a shared content model.

The site already has some of the right ingredients: a small site index and JSON-driven page data. The next step is to evolve that into an internal content system that supports centralized linking, navigation, related content, and search.

What Frontmatter Is

Frontmatter is the metadata block at the top of a Markdown file.

It usually sits between two --- lines and uses YAML-style key/value pairs.

Example:

---
title: "Content model for this site"
summary: "A content-model setup for a growing Eleventy site."
datePublished: "2026-03-20"
tags:
  - eleventy
  - content-model
---

The Markdown body is the article. The frontmatter tells the site how to treat that article.

Useful frontmatter fields:

  • title: the page or post title
  • summary: short card or SEO description
  • datePublished: display and sorting date
  • tags: topic labels for filtering or related content
  • permalink: final public URL
  • seo: search and social metadata

For this site, frontmatter is one of the main ways Markdown posts join the same shared content model as JSON data.

Use One Shared Content Model

Every content item should follow the same base shape whether it comes from JSON or Markdown.

That shared vocabulary should cover id, type, section, series, slug, url, title, summary, publication dates, tags, topics, relatedIds, image data, and search settings.

With one consistent shape, the same content can feed templates, site indexes, search documents, related-content systems, and future AI retrieval.

{
	"id": "blog-color-palettes-franklin-mountains",
	"type": "post",
	"section": "blog",
	"series": "color-palettes",
	"slug": "franklin-mountains-texas-colors",
	"url": "/blog/color-palettes/franklin-mountains-texas-colors/",
	"title": "Franklin Mountains, Texas, Winter Colors",
	"summary": "Color palettes from a hike in El Paso.",
	"datePublished": "2026-01-12",
	"dateUpdated": "2026-02-03",
	"status": "published",
	"tags": ["texas", "winter", "landscape", "regional-color"],
	"topics": ["color", "travel", "nature"],
	"relatedIds": [],
	"image": {
		"src": "blog-color-palettes/franklin-mountains/excerpt/franklin-mountains-brush-l.webp",
		"alt": "..."
	},
	"search": {
		"include": true,
		"boost": 1.0
	}
}

Separate Content Storage from URL Structure

Content should be stored by content type rather than only by rendered URL.

That means pages, posts, products, and portfolios can each have their own source folder even if the final site routes live under blog, pages, store, or portfolio.

This matters because a product and a blog post may eventually share tags, themes, or related content even though they render under different top-level paths.

content/
  pages/
  posts/
    blog/
      color-palettes/
      web-tech/
      architecture-and-urbanism/
      art/
      travel-journal/
      screen-printing/
  products/
    movie-series/
  portfolios/
data/
  site/
  navigation/
  taxonomies/
  search/
/blog/color-palettes/franklin-mountains-texas-colors/
/pages/about-me/
/store/movie-series/the-field-print/
/portfolio/ux/case-study-name/

Centralize Linking with a Generated Site Index

Instead of hardcoding links throughout templates, generate one global site index from normalized content items.

Each content item should contribute core metadata such as id, url, type, section, slug, title, tags, topics, series, image, and publication data.

Templates can then query the index for section pages, item cards, breadcrumbs, read-next modules, related prints, or search exports.

{
	"byId": {},
	"byUrl": {},
	"byType": {},
	"bySection": {},
	"byTag": {},
	"all": []
}
  • Get all blog series under /blog/.
  • Get all published products in movie-series.
  • Get all items tagged screenprinting.
  • Build breadcrumbs from section plus series plus current item.

Treat Navigation as Data

Primary navigation should live in data, not inside markup partials.

The top-level structure can stay manually curated while child navigation can be generated from published content in each section or series.

That approach keeps the information architecture intentional without forcing every growing subsection to be hardcoded.

[
	{
		"label": "Blog",
		"url": "/blog/",
		"children": [
			{ "label": "Color Palettes", "url": "/blog/color-palettes/" },
			{ "label": "Web Tech", "url": "/blog/web-tech/" },
			{ "label": "Travel Journal", "url": "/blog/travel-journal/" }
		]
	},
	{
		"label": "Pages",
		"url": "/pages/"
	},
	{
		"label": "Store",
		"url": "/store/"
	}
]
  • Use manual data for top-level IA.
  • Generate section and series children from content metadata.
  • Use the same source for header nav, footer nav, and breadcrumbs.

Use Metadata Scoring for Similar Posts and Products

Related content should be mostly automatic rather than hand-curated.

A simple weighted model can use same type, same section, same series, overlapping tags, and overlapping topics.

Manual relatedIds can still exist for editorial overrides when an exact relationship matters.

  • Same series: +5
  • Shared tag: +3
  • Shared topic: +2
  • Same section: +1

Generate Search-Ready Documents

If the site may later use client-side search or AI search tooling, each item should emit a normalized search document.

The important part is the document shape, not the specific search provider chosen first.

Clean fields such as title, summary, tags, topics, body text, image, and date can support Pagefind, MiniSearch, Algolia, or future embeddings.

{
	"id": "blog-color-palettes-franklin-mountains",
	"url": "/blog/color-palettes/franklin-mountains-texas-colors/",
	"type": "post",
	"section": "blog",
	"title": "Franklin Mountains, Texas, Winter Colors",
	"summary": "Color palettes from a hike in El Paso.",
	"tags": ["texas", "winter", "landscape"],
	"topics": ["color", "travel", "nature"],
	"body": "Plain text body content stripped of markup",
	"image": "...",
	"datePublished": "2026-01-12"
}

Use JSON and Markdown Together

JSON is a strong fit for structured content such as products, navigation, taxonomies, galleries, and reusable page sections.

Markdown is a strong fit for essays, web-tech posts, journal entries, and longer editorial writing.

The key rule is that Markdown front matter should use the same shared fields as JSON records so everything can flow into one normalized site index.

  • Use JSON for structure-heavy data.
  • Use Markdown for body-heavy editorial content.
  • Normalize both formats into one internal content model.

Practical Eleventy Setup

A practical Eleventy implementation would start with a content registry layer that normalizes JSON and Markdown into one shape.

From there, the build can generate collections for posts, products, pages, and portfolio entries along with siteIndex, searchIndex, and relatedContent outputs.

That foundation makes it much easier to grow the site without reworking the information architecture every time a new section is added.

  • Add a normalized content registry.
  • Generate a global site index.
  • Drive navigation from data.
  • Generate related-content and search indexes from metadata.

How to Organize JSON Files Under _data

You do not need deep nesting just for the sake of structure. The shared content model helps with consistency, but it does not replace the need to organize content files in sensible folders.

The practical rule is to keep using subfolders under wip/_data, but use them for content domain and content type rather than as a substitute for metadata.

A good setup is to keep site-level configuration in wip/_data/site, page-specific content in wip/_data/pages, blog-series content in folders for each series, store content in its own area, and portfolio content in its own area as well.

The shared model standardizes fields across all those files, makes it possible to build one site index from many folders, and reduces hardcoding in templates. What it does not do is automatically decide where every file should live or eliminate the value of separating content by domain.

So for now, it is completely fine to keep adding content into the existing _data subfolders. Over time, it would be cleaner to migrate toward a normalized structure such as wip/_data/blog/web-tech or wip/_data/store/movie-series, but that does not need to happen immediately.

wip/_data/
  site/
  pages/
  blog/
    color-palettes/
    web-tech/
    travel-journal/
  store/
    movie-series/
  portfolio/
  • Keep folder separation.
  • Use the shared schema inside each file.
  • Let generated indexes unify everything later.
  • Do not refactor everything at once unless the current naming is actively slowing you down.

Centralize Blog Section Titles and Intro Copy

As the home page starts surfacing featured entries from different blog categories, the titles and intro blurbs for those categories should live in a shared data file rather than inside the home page itself.

That content is really about the blog sections, not the home page layout. A centralized file lets the same copy drive the home page, future category landing pages, navigation summaries, and any other section-level UI without duplication.

A good pattern is to keep one object per section with fields for the home-page title, a short intro, a longer section-page intro, a reusable label, a section URL, and CTA text. Then the home page consumes the short version while dedicated index pages can use the longer one.

This approach keeps the home page lightweight while still supporting a scalable content system. As more posts are added, the home page can continue showing the latest or featured entry from each category, while the deeper story about each category lives in its own themed section page.

{
	"artPosters": {
		"id": "art-posters",
		"homeTitle": "Screen printing blog latest",
		"label": "Art Posters",
		"shortIntro": "Short home-page intro...",
		"longIntro": "Longer section-page intro...",
		"sectionUrl": "/blog/art-posters/",
		"homeCtaLabel": "View series"
	},
	"colorPalettes": {
		"id": "color-palettes",
		"homeTitle": "Latest Color Palette",
		"label": "Color Palettes",
		"shortIntro": "Short home-page intro...",
		"longIntro": "Longer section-page intro...",
		"sectionUrl": "/blog/color-palettes/",
		"homeCtaLabel": "Read palette"
	},
	"webTech": {
		"id": "web-tech",
		"homeTitle": "Web Tech blog latest",
		"label": "Web Tech",
		"shortIntro": "Short home-page intro...",
		"longIntro": "Longer section-page intro...",
		"sectionUrl": "/blog/web-tech/",
		"homeCtaLabel": "Read post"
	}
}
  • Keep home-page section intros out of the home page data file when they describe reusable blog categories.
  • Use a shared blog-sections file for labels, intros, URLs, and CTA copy.
  • Let the home page use short intros and future category index pages use the long versions.
  • Treat the home page as a latest-or-featured gateway, not the permanent home of category explanation.

Closing Thought

The shortest version is this: centralize around content metadata rather than page templates.

Once blog posts, pages, products, and future portfolio entries all share the same internal model, linking, navigation, search, and related content become much easier to scale.