How This Site's Content Is Built and Validated

Every page on this site is a markdown file that passes a validator, gets its JSON-LD generated from its own content, and imports into Django only when it changed.

By Joshua Hayes

The page you are reading started as a markdown file. Before it reached this site, a script checked it against a set of written rules, built its structured data, and an importer loaded it into Django.

I use AI to help draft content, so the pipeline is built the way I build everything with AI: rules first, a validator as the gate, and a person reviewing every file before it ships.

What does the content pipeline do?

It turns markdown files into published pages without losing control of accuracy or SEO. Each file maps to a Django model, a validator checks it against length, structure, privacy, and wording rules, a script generates its JSON-LD, and an importer loads only the files that changed.

Nothing is published automatically. Every file goes through my review first.

How does a markdown file map to the site?

Each file starts with front matter whose keys mirror the fields on a Django model: Page, Project, or Post, plus a nested block for the SEO model. The file name prefix says which model it is, and the body is the page content.

A trimmed example of a note’s front matter:

model: post
title: How This Site's Content Is Built and Validated
slug: site-content-pipeline
category: AI-Assisted Development
qa_headings: true
seo:
  meta_title: How This Site's Content Is Built and Validated - Turngear
  meta_description: The pipeline behind this site...

A few keys are notes for the build and never reach the database. They switch on extra structured data for that one file, such as Question and Answer markup.

What rules does every page have to pass?

The rules live in a written spec, and a Python validator enforces them on every file. A file with a problem does not move forward.

Search rules

  • Title tags of 50 to 60 characters and meta descriptions of 120 to 155, inside the model’s own limits.
  • Exactly one H1 that matches the title, and no skipped heading levels.
  • Short slugs, alt text of 80 to 125 characters, and every internal link checked against the full set of pages.

Writing rules

  • Paragraphs of three sentences or fewer, for mobile reading.
  • A banned list of words and phrases that read as generated or inflated.
  • No dash characters that mark text as machine-written.

Privacy and accuracy rules

  • Only one public contact email, and no phone numbers or street addresses.
  • No other person named without permission.
  • Facts and numbers come only from my vetted profile, the same source of truth my resume kit uses.

How is the structured data generated?

The validator builds each page’s JSON-LD from the file itself, so the markup cannot drift from the content. Pages get the right page type, projects get Article, notes get BlogPosting, and every page gets breadcrumbs from its place in the site tree.

Files can opt into more. Headings written as questions become schema.org Question and Answer pairs, a set of steps can become a HowTo, and pages about me carry my credentials and skills on the Person entity.

Anchors that match the live page

Each Question points to an anchor on the page. The validator renders the markdown with the same extensions the site uses, so the anchor ids in the JSON-LD are the ids the browser sees.

I also check this the slow way. A script renders each new page through the site’s real template filter and confirms that every Question and HowTo anchor exists in the HTML.

How do changes get imported?

A Django management command reads the finished files and writes them to the database. It has a dry-run mode that validates everything without writing.

  • Hash-aware. Each file’s SHA-256 hash is stored, and a file that has not changed is skipped.
  • Ordered. Categories and content blocks load first, then pages with parents before children, then notes, projects, and navigation.
  • Status-safe. A page keeps its current status unless I ask the import to publish.

Diagrams come from a Python script that draws SVGs with a title and description for screen readers and colors that follow dark mode.

How is the rendered page kept safe and clean?

Markdown is converted to HTML and then passed through an allowlist, so only expected tags and attributes survive. Heading ids are on that allowlist so in-page links work.

The page template owns the only H1, and any H1 in stored markdown is removed at render time. JSON-LD is serialized so its text cannot break out of its script tag, and a test proves it.

The robots.txt file and the sitemap are generated from the database and leave out drafts and pages marked noindex.

How do I know a change to the pipeline did not break anything?

Changes to the pipeline get the same treatment as product code. The site’s test suite covers rendering, JSON-LD output, the sitemap, and the single-H1 rule, and at the time of writing it ran 43 tests.

When I changed the validator to add Question and Answer markup, I saved the JSON-LD it produced for every existing file first. After the change, the output for those files was byte-for-byte identical, so only the new pages were affected.

How does a page reach production?

Drafts move through folders: in progress, drafted, then completed or needs work after my review. Nothing moves to completed until I have read it.

Everything is checked locally first. Then the change is pushed to the main branch, the server pulls it and rebuilds, and the import runs there.

Where does AI fit in this pipeline?

AI helps draft the content and the code, inside the rules above. It does not decide what is true, what gets published, or whether a check can be skipped.

The validator has already paid for itself. It caught dead links to a renamed page before anything shipped, which is exactly the kind of small miss a person skims past.

The reasoning behind this setup is in Why Constraints in AI Development Matter. For the technical SEO side of this work, see the technical SEO service.