Notifio watches rental listing pages and tells you the moment something new appears. It is one of four apps I build, and the other three are a psychometric test trainer, a food scanner for people with gut conditions, and a live quiz night for pubs. Four domains, four repositories, four Next.js deployments, and no shared audience whatsoever.
Four small sites on four unrelated domains that link to each other is, structurally, indistinguishable from a link scheme. Nothing on any of them says "these are the same publisher" in a form a crawler can act on. The About page saying "also from Sonacode Ltd" is prose, and prose is not an assertion.
The fix is about forty lines of JSON-LD, and the interesting part is not the schema. It is the decision that the publisher is an identifier rather than a link.
Each site emits an Organization for itself, with the publisher hanging off it as parentOrganization:
And publisherOrganizationLd returns the publisher with every app it publishes underneath it:
Two @id values are doing all of the work here. https://notifio.app/#organization is what this site calls itself everywhere, on every page, forever. PUBLISHER.id is what all four sites call the company. Repeat those two strings consistently and a consumer can merge the nodes; get one character wrong on one site and that site is an orphan.
It is a fragment on a page belonging to one of the four apps, not a company homepage. There is no sonacode.com. From the file header:
The temptation was to register a domain, put up a one-page company site, and point @id at it. That would have been three more things to maintain and one more page that could go stale or 404, in exchange for nothing a crawler needs. An @id in JSON-LD is a name for an entity. It looks like a URL because that is the convention, and it is allowed to resolve to something, but it is not obliged to. The only property that matters is that all four sites spell it the same way.
If you have ever hesitated over "but there's nothing at that URL", that is the hesitation to let go of. It is the same category of mistake as assuming every href is a vote, which I wrote about in a link is not a link until you have read its rel attribute.
I checked what the four live sites actually serve, and three of them nest the publisher inside parentOrganization while the fourth uses a @graph with the publisher as a sibling node and a bare @id reference:
My first reaction was that one of the four repositories had drifted. It has not. Those two documents say exactly the same thing, because a consumer merges nodes on @id before it interprets anything: nesting an object and referencing it by id are two spellings of one edge. The site with the richest graph, which also emits WebSite and BreadcrumbList nodes, uses the flat form because flat is easier to extend.
That is worth internalising before you spend an afternoon making four templates byte-identical. The contract between the sites is the identifier, not the document shape.
You will get the same subOrganization list of four @id values on notifio.app, cogniprep.app, munchable.app and pub-trivia.app.
The rule I hold to on all four sites is that structured data has to describe something a human can see on the page. /about exists partly to give the publisher claim something to stand on, and the page renders from the same file the markup does:
SIBLINGAPPS is SONACODEAPPS minus whichever app this repository builds, which is the one line that differs between the four copies:
So adding a fifth app is one entry in one array, and it appears in the markup and on the page of all four sites at once. There is no way to be listed in the graph and missing from the page, because they read the same array.
The section's opening paragraph is generated, and it says the unflattering thing on purpose:
Writing "our family of complementary products" there would have been a lie that a reader can check in one click. A visitor who arrived at the rental app and is being shown a pub quiz will believe an honest sentence and bounce off a dishonest one. So each app carries a detail field written for exactly that reader:
