Publishing is the step where small errors become public, and the cheapest ones to make are a missing property and a wrong date. On 2026-10-03 the schema agent of our audit scored this site's structured data at 78 out of 100, with a mature graph and two required properties absent. The same day, two other claude-seo runs flagged a blog post of ours for carrying a publish date seven days in the future. That date came out of our own product: the article was planned for 2026-10-10 on the calendar, the delivery wrote that planned day into the file, and the post went live on 2026-09-28. This lesson walks both findings, then the part of publishing a tool never does for you.
A validator and a rich result ask different questions
Valid markup is markup a parser accepts, and eligible markup is markup Google can turn into a feature. A block can clear the first bar and sit well under the second, because a rich result has a list of required properties and your markup either carries them or it does not. Our BlogPosting parsed cleanly, with headline, datePublished, dateModified, author as a Person reference and publisher as an Organization reference, all correctly typed.
It was still ineligible for the Article rich result, because image was missing on every post on the site. One absent property, and the whole blog sat outside a feature it otherwise qualified for. Read the required list for the specific result you want before you grade your own markup, and treat anything else the validator tells you as background.
Our own graph scored 78 out of 100
The audit found structured data on seven page templates, and the shape of it was the good news. Organization, WebSite and SoftwareApplication with an Offer on the home page; WebPage and BreadcrumbList on pricing; WebPage, FAQPage and BreadcrumbList on a feature page; BlogPosting, Organization, Person and FAQPage on the post; a WebApplication with an Offer on a free tool. Every @context pointed at the canonical schema.org URL, every URL was absolute, every date was ISO 8601, and the entities were cross-linked by @id so one graph described one site.
Three things capped the score. BlogPosting had no image and the posts had no BreadcrumbList, while the other four templates did. SoftwareApplication and WebApplication had no aggregateRating or review, which the Software App rich result requires. And FAQPage, present on four templates, no longer earns anything in the SERP.
Some gaps are a line of code and some need data you do not have
Splitting the gap list in two is the first useful move, because the two halves have different costs. image on BlogPosting was free for us: every post already renders an OG card at a stable URL, so filling the property was pointing at an asset that exists. BreadcrumbList on posts was the same kind of work, since four other page templates already emitted one.
aggregateRating is the other kind. The property wants real review data, and a site with no reviews has nothing honest to put there. Our audit wrote the instruction into the finding itself: add it only once genuine review data exists, and never fabricate ratings. An agent asked to "complete the schema" will happily invent a 4.8 out of 237 reviews, and that number is then a fabricated claim on a page whose whole argument is that it reports measured things. Keep the property empty until the data exists.
FAQPage is valid markup with no SERP feature since May 2026
Google retired FAQ rich results for all sites on 2026-05-07, so FAQPage markup wins no search feature today. Our audit records it as information on the four templates that carry it, recommends leaving those alone, and advises against adding it to new pages for SERP reasons. The geo agent in the same run proposed adding FAQPage to more pages, and the orchestrator discarded that proposal with the retirement date attached.
Write the visible FAQ anyway. A reader with a question reads it, and an answer engine looking for a short quotable passage reads it too, which is what lesson 12 is about. If you want the markup for the sake of whatever else parses your JSON-LD, the free FAQ schema generator builds it from the questions you already wrote, and you can decide about the SERP separately.
A calendar date became a publish date
The article was planned for 2026-10-10, and that planned day is the one the delivery wrote into the file's publishedAt field. The post itself went live on 2026-09-28 and narrates a weekend of the 26th and 27th of September. So the page shipped carrying a date in the future on the line a reader sees, in datePublished and dateModified, in article:published_time, and in four lastmod values of the sitemap.
Three runs found it on the same day, from three angles. The audit's orchestrator confirmed it against the live site and tied it to the sitemap's future lastmod values as one root cause. The content command scored it a high-severity trust issue, since the visible date contradicts a body that says the thing went live on September 27. The geo command rated it critical and guessed the mechanism correctly: the product is writing the scheduled date instead of the real one. We corrected the date in the repository to 2026-09-28.
The suggested fix carried the bug with it
The schema agent closed its report with a ready-to-paste JSON-LD block for the missing image, and that block reused 2026-10-10 for both dates. The orchestrator caught it and left a note in the verification section telling the reader to use the real publish date instead.
Copy-pasteable output is where a wrong value travels fastest. A date you would have questioned in a sentence slides through inside a code fence, because a code fence reads as something a machine produced and a machine checked. Read the sample before you paste it, and check the values the sample carried over from the page it was describing.
Delivery puts a draft file in your repository and stops
A delivery target is five answers: repository, branch, path template, body format and the frontmatter mapping. Ours reads GitHub ch0rch/seodraft-app, branch main, content/blog/{locale}/{slug}.mdoc, Markdoc body, Keystatic preset, and that preset maps date to publishedAt, updatedDate to updatedAt, and drops image. delivery_status reads those five back for you, plus whether a token is saved, and it never returns the token itself.
Every delivery writes draft: true into the file whatever the article's own flag says, there is no option to turn that off, and nothing in your repository is ever deleted. Re-delivering overwrites the same path, so repeating it is safe; when a slug moves, the new path is written and the old one is named for you to delete by hand. The CMS delivery feature page describes the targets in more detail.
Approval is one call that checks the rules first
approve_post refuses unless the last run_gate check passed, which makes approval a gate rather than a button. When it passes, the same call moves the article to ready and delivers it to the configured destination. Our own post reached that state with lastGateOk: true and landed at content/blog/en/how-to-distribute-an-mcp-server.mdoc on 2026-09-28 at 03:11 UTC, with its Spanish translation linked to it as the same article in another language.
After that the tool is finished and you are not. The file in your repository waits for a person to open it, check the dates, check the links, upload the images the article's slots asked for, and publish from the CMS. Our date bug lived in that gap for a few days, which is the honest version of the lesson: the gap exists so a human can catch what a pipeline wrote, and it only works when the human looks.
The Spanish tree declared itself English in the raw HTML
The audit's single critical item had nothing to do with the post. Our /es routes served <html lang="en"> in the HTML that comes off the server, with the correct language applied afterwards by a client script. A crawler reading raw HTML, and any client that does not run JavaScript, saw Spanish content labelled as English across the whole Spanish tree.
We fixed it by rendering lang on the server from the route itself, so the attribute is correct in the first byte. The check is one line of curl against your own site, and it is worth running after any change to a root layout. A language declaration is the kind of thing that is either right everywhere or wrong everywhere, and nobody notices in a browser.
What to read on the morning you publish
Four readings make a publishing check, and they take about ten minutes on a normal post. Read the dates on every surface that carries one, including your sitemap, which the free sitemap checker will list for you without installing anything. Read the required properties of the rich result you want, and leave empty anything you cannot back with real data. Open the delivered file and confirm the draft flag is still there, then publish from your CMS yourself.
The fourth reading is the one people skip: open the live URL afterwards and look at what the page actually says. Our post was correct in the draft, correct in the delivery and wrong on the page, and the difference was a field a pipeline filled with the day somebody had planned. Lesson 12 takes the same published page and asks a different question about it: whether an answer engine can quote it at all.