A drive chain and sprocket linking two shafts on a machine

Guide

Link your schema with @id instead of repeating it

Declare the Organization and Person once with stable @id values and reference them everywhere else. One entity described in one place, not forty near-copies.

Published July 27, 2026

Declare the Organization and Person once, each with a stable @id, and have every other piece of structured data reference them by that identifier instead of restating them. One entity, described in one place, linked reciprocally — rather than forty slightly different copies of the same company.

The problem: forty copies that disagree

Most sites emit an Organization node on every page, from whatever helper that page happens to use. The copies start identical and drift: one has the old tagline, one has a logo path that moved, one names the founder and one does not. Nothing errors, and nothing tells you which version is the entity — because as far as the markup is concerned they are unrelated objects that happen to share a name.

There is usually a bigger version of the same problem off-site. An audit of this site found the website, the LinkedIn profile, and the GitHub account each describing a different occupation, with nothing connecting them — three plausible entities where there should have been one, and no signal saying which was authoritative.

Give each entity a stable identifier

The @id is the mechanism. It is a URL you control, used as an identifier rather than a link, and it never has to resolve to anything. Define the two constants once and never change them — changing an @id later breaks every reference pointing at it.

lib/schema.ts
export const BASE_URL = "https://www.tallkarol.com"
const ORG_ID = `${BASE_URL}/#organization`
const PERSON_ID = `${BASE_URL}/why#karol`

Note that each identifier sits on the page that best represents the entity: the organisation on the homepage, the person on the page about that person. That is convention rather than requirement, and it makes the graph legible to a human reading the source, which is worth more than it sounds when someone else maintains it.

Declare the full nodes once, globally

Both full nodes are emitted from the root layout, so every page carries them exactly once. The reciprocal edges are the important part: the organisation names its founder by identifier, and the person names the organisation they work for by the same identifier. That turns two isolated objects into a graph with a relationship in it.

lib/schema.ts
export function organizationNode() {
  return {
    "@type": "Organization",
    "@id": ORG_ID,
    name: "Tall Karol",
    url: BASE_URL,
    logo: { "@type": "ImageObject", url: `${BASE_URL}/tallkarol-monogram-logo.png` },
    description: "Dev studio providing fractional and project-based engineering…",
    ...(SOCIAL_PROFILES.length > 0 && { sameAs: SOCIAL_PROFILES }),
    founder: { "@id": PERSON_ID },
    contactPoint: {
      "@type": "ContactPoint",
      contactType: "Business Inquiries",
      url: `${BASE_URL}/contact`,
    },
  }
}

export function personNode() {
  return {
    "@type": "Person",
    "@id": PERSON_ID,
    name: "Karol",
    jobTitle: "Senior Engineer & Systems Architect",
    url: `${BASE_URL}/why`,
    ...(SOCIAL_PROFILES.length > 0 && { sameAs: SOCIAL_PROFILES }),
    worksFor: { "@id": ORG_ID },
  }
}

The conditional spread on sameAs is deliberate. An empty array is a published claim that the entity has no profiles anywhere, which is worse than saying nothing — so the key is omitted entirely until there is something true to put in it. That pattern is worth applying to any optional schema property you might not have data for yet.

Reference, don't repeat

Every other schema on the site points at those identifiers through two tiny helpers. A reference carries just enough to be readable — type, identifier, name, URL — and defers everything else to the canonical node.

lib/schema.ts
/** Compact reference to the canonical Organization node */
export function organizationRef() {
  return { "@type": "Organization", "@id": ORG_ID, name: "Tall Karol", url: BASE_URL }
}

/** Compact reference to the canonical Person node */
export function personRef() {
  return { "@type": "Person", "@id": PERSON_ID, name: "Karol", url: `${BASE_URL}/why` }
}

Now an article's author, a service's provider, and a breadcrumb's publisher all resolve to the same two nodes. When the job title changes, it changes in one function rather than in every page that mentioned it — the same derive-don't-duplicate reasoning behind running the whole knowledge base from one registry.

Why this matters more now than it did

Search engines have been consolidating entities for years, but the audience for unambiguous structured data has grown: assistants that answer questions about a company are assembling facts from whatever they can reconcile, and they reconcile better when the site states plainly which profiles are its own and how the people and the organisation relate.

Keep the claim modest, though. This is not a ranking lever and nobody should sell it as one — it is the difference between being described accurately and being described from three contradictory sources. The same restraint applies to FAQ markup, whose rich results have narrowed considerably, and which is still worth keeping for exactly the same reason.

A short checklist

One Organization node and one Person node, each with a stable @id, emitted once from the root layout. Reciprocal edges between them. sameAs listing only profiles you control and keep current, omitted entirely when there are none. Every other schema referencing by identifier rather than restating. And the same facts — name, job title, description — matching what those external profiles actually say, because the reconciliation you are inviting will notice if they do not.

Structured data is only worth the effort when the pages underneath it are, which is why schema is built in from the start on every site I ship rather than added as a plugin afterwards.

Questions

Does any of this produce a rich result?

Organization and Person markup mostly doesn't, and that's the wrong reason to add it. Its value is disambiguation: making it unambiguous which company and which person a page is about, for systems that assemble entities rather than render snippets. Treat rich results as a separate question with its own eligible types.

What should the @id actually be?

A URL you control with a fragment, on the page that best represents the entity — a homepage fragment for the organisation, the profile page fragment for the person. It never needs to resolve to anything; it's an identifier, not a link. What matters is that it's stable, because changing it later severs every reference.

How many sameAs profiles are worth listing?

The ones you actually control and keep current. Two accurate profiles beat eight, half of which are abandoned and describe you differently — and a stale profile actively works against you, because the reconciliation you're inviting will find the contradiction.

Where should the global nodes be emitted?

Once, from the root layout, so every page carries them. Page-specific schema then references those identifiers instead of restating the entity. Emitting the full nodes per page isn't harmful in itself, but it's how the copies start to drift apart.

Written by

Karol

Senior engineer and systems architect behind Tall Karol. Everything published here is grounded in real client work — no roundups, no tools that haven't run in production.

Why Tall KarolWork with Tall Karol

Related notes

More on web development

Related service: Web Development

Want this kind of engineering on your project?

Tall Karol takes on fractional and project-based engagements for startups and agencies.

Book a working session