Your schema markup is a second copy of the page
Schema markup is the page written a second time, for machines — and second copies drift. We don't keep ours honest by checking it. The page and its structured data come from the same record, so neither can say something the other doesn't.
by Nephele K.
Schema markup is the page, written a second time, in a dialect only machines read. And second copies drift. The author field says "admin" — whoever installed the plugin in 2019 and has apparently written every article since. The modified date changes on every deploy, or never. The FAQ node lists five questions; the page shows three. There's a price in an Offer on a page that quotes nothing, because the template came with one. Google's structured data guidelines are blunt about it: mark up what's visible, describe what's there, and markup that doesn't can earn a manual action that strips your rich results. We don't keep our schema honest by checking it. We made lying impossible.
Every post on this site is one record: title, excerpt, author, date, tag, paragraphs. The page is rendered from it. So is the structured data in the head of this page, the sitemap entry, the RSS item, and the social image you saw if you got here from a link. Nobody types anything into the BlogPosting node. The headline in the JSON-LD is the headline on the page because they're the same variable. Change the title in the record and all five outputs change in the same deploy. There is no step called "update the schema", so there's no step to forget. Services and projects work the same way — the OfferCatalog on a service page is the "what we build" list, item for item.
One record, one graph
The diagram above is one record. Site-wide, it's a graph. Every node has an @id, and the others point at it instead of repeating it. We describe the organisation once; every service, every post, the WebSite and the Blog reference that one description. A post's author isn't a string — it's a Person, with the job title from the team page and a worksFor that points back at us. The Greek page declares el-GR, gets its own canonical, and points at the English page with hreflang while the English one points back. In Next.js the easy mistake is a relative canonical on the Greek page that resolves to the English URL. Do that and you've just told Google your Greek site is a duplicate.
Why this matters more now than it did. For a decade, schema was an SEO chore: it earned rich results and nothing else. The newer readers use it differently. An answer engine deciding what you are reads the page and the markup, and when the two disagree it has two sources and no reason to trust either. That's most of GEO, as far as we're concerned. We said in 2025 that models reward distinct claims — a schema that agrees with its page is the same claim made twice, once in prose and once in a form a machine can check. Agreement is cheap to verify. That's exactly why it's valued.
Where the schema drifts
The ledger above is the audit we run before we touch anything, and on the sites we're asked to look at it's usually one of these four rows. Author: a login name, or the agency's. Dates: a dateModified that updates on every build, which tells the crawler the page is perpetually new and therefore never settled — or one frozen at launch. Counts: FAQ, product and review nodes that outnumber what's on the page, because the markup was generated from the database and not from the template. Offers and ratings: a 4.8 from reviews nobody can find, a price on a service page that quotes nothing. We turned the first pass of that audit into a free tool, Second Copy: paste a URL and it shows where your markup and your page disagree. The fix is almost never "edit the JSON". It's finding what the page is actually rendered from and making the schema read that. When the CMS can't, that's the un-frameworking conversation.
What we don't do, on purpose. We don't mark up anything a reader can't see — no FAQ schema for questions that live in a support portal, no Review nodes for testimonials we don't show, no hidden paragraphs "for the models". We don't serve crawlers a different page; the HTML a bot gets is the HTML you got. The one place we go past the visible text is speakable: we mark the headline and the excerpt of every post as the two sentences we'd want read aloud, and they're the two sentences at the top of the page. If a reader can't see it, a model shouldn't be told it.
What changed since we started saying this out loud: we stopped auditing the JSON. We open the page, find the structured data (the script tagged ld+json), and ask one question — where is this generated from? If the answer is "the same place as the page", the audit is mostly done. If the answer is "a plugin", we know what the next two weeks look like. One record, rendered twice. The page is the spec. The schema is its transcription.