A large number of brands run their blog as a separate WordPress installation — a subdirectory like /blog/ or a subdomain like blog.yourdomain.com. It’s a common architecture. It also produces broken schema by default, in a way that splits your brand into two separate entities in Google’s eyes.
The blog accumulates authority under one entity. Your main site accumulates under another. No audit catches it, no validator flags it, and nothing in your Google Search Console will tell you it’s happening. The schema is technically valid — it’s just pointing at the wrong entity.
How I found this
I came across this while auditing blogs for a client running their main site and blog as separate WordPress installations. RankMath was installed on both. The schema on the blog looked clean — valid JSON-LD, correct property names, no missing fields. But the @id values on the Organization and WebSite nodes were anchored to the blog’s subdirectory URL, not the main domain. Two installs, two entity declarations, one brand split in two at the schema level.
RankMath isn’t uniquely at fault here. Any SEO plugin that constructs @id from the WordPress Site URL will produce the same result on a subdirectory or subdomain install — because that’s exactly what WordPress gives it. Yoast, AIOSEO, and others follow the same logic. The issue is structural, not a bug in any specific plugin.
How schema.org entity identity works
Google uses schema.org’s @id property to identify entities across the web. Think of @id as a permanent name tag for a thing: an organization, a website, a person. Two pages declaring the same @id are the same entity to Google. Two pages declaring different @id values are different entities — even if the name is identical.
For a brand’s web presence, the two most important entity declarations are:
Organization— who owns and publishes the siteWebSite— the site itself as a distinct web property
On a correctly configured main site, these look like:
{ "@type": "Organization", "@id": "https://example.com/#organization" }
{ "@type": "WebSite", "@id": "https://example.com/#website" }
Every piece of content published under that brand should trace back to these two @id values. That’s how Google connects blog posts, product pages, author profiles, and reviews into a coherent picture of a single organization.
The subdirectory blog install problem
When you install WordPress in a subdirectory like /blog/ and configure an SEO plugin, it reads the WordPress Site URL to construct its schema @id values. Since the Site URL is https://example.com/blog, the plugin outputs:
{ "@type": "Organization", "@id": "https://example.com/blog/#organization" }
{ "@type": "WebSite", "@id": "https://example.com/blog/#website" }
Your blog just declared itself a separate organization. Same name, different @id. From Google’s perspective, example.com/#organization and example.com/blog/#organization are two distinct entities that happen to share a name, the same way two different companies might both be named “Apex Solutions.”
Every article your blog publishes credits its authority to example.com/blog/#organization, not to example.com/#organization. The E-E-A-T signals, the backlinks, the topical authority — they accrue to an entity that doesn’t match your main site.
The three schema problems this creates
1. Fragmented entity authority
Your main site and blog accumulate authority in separate entity buckets. When Google evaluates your brand for Knowledge Panel eligibility or E-E-A-T scoring, it has to reconcile two declarations rather than reinforce one. At best that’s wasted signal. At worst Google can’t determine which entity is the authoritative one, and neither gets full credit.
2. Wrong page type on the blog homepage
Most SEO plugins default to outputting Article schema on a blog index page. A blog homepage is a CollectionPage — it lists articles, it isn’t one. Outputting Article schema with a generic author on what is clearly an index page is semantically incorrect and leaves Google guessing about the page’s role in your site structure.
3. Blog content marked as a separate website
Your blog’s WebPage nodes declare isPartOf: example.com/blog/#website instead of isPartOf: example.com/#website. Google attributes the blog’s content to a separate web property rather than a section of your main site — the opposite of the relationship you need for authority consolidation.
The correct schema structure
The right pattern for a subdirectory blog is clean: the blog declares no Organization or WebSite entities of its own. It references the main site’s existing entities.
The blog’s CollectionPage should look like:
{
"@type": "CollectionPage",
"@id": "https://example.com/blog/#collectionpage",
"url": "https://example.com/blog/",
"name": "Example Blog",
"isPartOf": { "@id": "https://example.com/#website" },
"about": { "@id": "https://example.com/#organization" }
}
The Organization and WebSite nodes, if they appear at all in the blog’s output, must carry the main site’s @id values — example.com/#organization and example.com/#website. Not example.com/blog/#organization.
Google reads one organization, one website, with blog content as a section of that site. Your blog’s backlinks and E-E-A-T signals credit the same entity as your product pages.
Subdomains have the same problem
A blog at blog.example.com running as a separate WordPress install produces the same fragmented schema by default. The @id values anchor to blog.example.com rather than example.com, and the same entity split occurs.
The fix is structurally identical: override the Organization and WebSite @id values to reference the root domain, and point isPartOf to example.com/#website.
If your subdomain has built its own independent authority — separate backlink profile, distinct readership — keeping it as a separate entity may make sense. A blog that exists to serve your product funnel should consolidate.
How to check if you have this problem
- Go to your blog’s homepage
- View page source and search for
application/ld+json - Find the
Organizationnode and check its@idvalue
If the @id contains your subdirectory or subdomain path — example.com/blog/#organization or blog.example.com/#organization — you have the issue.
Then check your main site’s Organization @id. If it’s example.com/#organization, the mismatch is confirmed.
Fixing it
The core problem is that every SEO plugin constructs @id from WordPress’s Site URL setting. For a /blog/ install, that Site URL is correctly set to example.com/blog — changing it would break the installation. So the fix has to happen after the plugin generates its schema, by overriding the @id values before they’re output.
The most reliable approach regardless of which SEO plugin you use is a PHP filter added via the Code Snippets plugin on your blog installation:
add_filter( 'rank_math/json_ld', function( $data, $jsonld ) {
$main_site = 'https://example.com/';
$tld = 'https://example.com';
if ( isset( $data['Organization'] ) ) {
$data['Organization']['@id'] = $tld . '/#organization';
$data['Organization']['url'] = $main_site;
if ( isset( $data['Organization']['logo']['@id'] ) ) {
$data['Organization']['logo']['@id'] = $tld . '/#logo';
}
}
if ( isset( $data['WebSite'] ) ) {
$data['WebSite']['@id'] = $tld . '/#website';
$data['WebSite']['url'] = $main_site;
if ( isset( $data['WebSite']['publisher']['@id'] ) ) {
$data['WebSite']['publisher']['@id'] = $tld . '/#organization';
}
}
if ( is_home() || is_front_page() ) {
if ( isset( $data['WebPage'] ) ) {
$data['CollectionPage'] = $data['WebPage'];
$data['CollectionPage']['@type'] = 'CollectionPage';
$data['CollectionPage']['@id'] = 'https://example.com/blog/#collectionpage';
unset( $data['WebPage'] );
}
}
foreach ( array( 'WebPage', 'CollectionPage' ) as $type ) {
if ( isset( $data[$type] ) ) {
if ( isset( $data[$type]['isPartOf']['@id'] ) ) {
$data[$type]['isPartOf']['@id'] = $tld . '/#website';
}
if ( isset( $data[$type]['about']['@id'] ) ) {
$data[$type]['about']['@id'] = $tld . '/#organization';
}
}
}
return $data;
}, 99, 2 );
Replace https://example.com with your actual main site URL throughout. The filter hook name (rank_math/json_ld) is RankMath-specific — if you’re on Yoast or AIOSEO, the hook name differs but the logic is identical.
If you’d rather not write the filter by hand, I built a small generator tool that takes your URLs, organization name, and social profiles and outputs either the PHP filter or a raw application/ld+json block you can add directly to your theme — whichever fits your setup.
Why this doesn’t show up in audits
Standard SEO audits check whether schema is syntactically valid and whether required properties are present. They don’t check whether your @id values are semantically coherent across installations. A blog with example.com/blog/#organization passes every automated check because the schema is technically valid — it’s pointing at the wrong entity, but no validator knows what the right entity should be.
The gap between “validates correctly” and “represents your site accurately” is where this issue lives. Automated audits don’t cross it.
The broader principle
Schema @id values are entity identifiers, not page identifiers. When you run multiple WordPress installs under one brand, each install needs to know which entity it belongs to — and that entity lives at the root domain, not at the install’s own URL. Any SEO plugin that constructs @id from the WordPress Site URL will get this wrong by default on subdirectory and subdomain installs.
Check your blog’s Organization @id. If it contains your subdirectory path, everything you’ve published is crediting the wrong entity. The filter above corrects it in one step.
Leave a Reply