DEV Community

Cover image for When an extra llms.txt or schema plugin is worth it next to the SEO plugin you already run
AumCreate
AumCreate

Posted on

When an extra llms.txt or schema plugin is worth it next to the SEO plugin you already run

A page that emits two Organization nodes with different @id values is the most common symptom of two plugins describing the same business. The graph becomes ambiguous: a consumer has to guess which node is the subject of the page, and any property that appears on only one of them may be read as missing.

The same test decides whether an llms.txt plugin is redundant. If nothing on the site serves that path today, a plugin that adds it is the first producer, not a second one. If two things register the route, one file is served and neither admin screen tells you which one answered.

So: read the published output first, then decide what to install.

Find out what the page already publishes

Counting blocks is a start:

curl -s https://example.com/about/ | grep -c 'application/ld+json'
Enter fullscreen mode Exit fullscreen mode

A count tells you how many blocks exist, not what is inside them. This script walks every block, follows @graph, and groups nodes by @type:

<?php
// scan-jsonld.php — list every JSON-LD node on a page and flag duplicate @type entries.
$url = $argv[1] ?? null;
if ( ! $url ) {
    fwrite( STDERR, 'usage: php scan-jsonld.php https://example.com/page/' . PHP_EOL );
    exit( 1 );
}

$ch = curl_init( $url );
curl_setopt_array( $ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_FOLLOWLOCATION => true,
    CURLOPT_TIMEOUT        => 20,
] );
$html = curl_exec( $ch );
if ( false === $html ) {
    fwrite( STDERR, curl_error( $ch ) . PHP_EOL );
    exit( 1 );
}
curl_close( $ch );

preg_match_all( '#<script[^>]*application/ld\+json[^>]*>(.*?)</script>#is', $html, $matches );

$nodes = [];
$walk  = function ( $node, $src ) use ( &$walk, &$nodes ) {
    if ( ! is_array( $node ) ) {
        return;
    }
    if ( isset( $node['@graph'] ) && is_array( $node['@graph'] ) ) {
        foreach ( $node['@graph'] as $child ) {
            $walk( $child, $src );
        }
        return;
    }
    if ( isset( $node['@type'] ) ) {
        $nodes[] = [
            'type' => implode( '|', (array) $node['@type'] ),
            'id'   => $node['@id'] ?? '(none)',
            'src'  => $src,
        ];
    }
    foreach ( $node as $value ) {
        if ( is_array( $value ) ) {
            $walk( $value, $src );
        }
    }
};

foreach ( $matches[1] as $i => $raw ) {
    $data = json_decode( trim( $raw ), true );
    if ( ! is_array( $data ) ) {
        echo 'block ' . $i . ': not valid JSON' . PHP_EOL;
        continue;
    }
    $walk( $data, 'block ' . $i );
}

$byType = [];
foreach ( $nodes as $node ) {
    $byType[ $node['type'] ][] = $node;
}

foreach ( $byType as $type => $list ) {
    $ids  = array_unique( array_column( $list, 'id' ) );
    $flag = count( $ids ) > 1 ? '  <-- same @type, different @id' : '';
    printf( '%-26s %d node(s)  ids: %s%s' . PHP_EOL, $type, count( $list ), implode( ', ', $ids ), $flag );
}
Enter fullscreen mode Exit fullscreen mode

Run it against a handful of representative URLs: home, a service page, a product page, a post, an archive. Duplicates are rarely site-wide. A theme that injects BreadcrumbList on posts plus a plugin that injects it everywhere produces two nodes on some templates and one on the rest, which is why testing only the home page hides it.

llms.txt and sitemaps need the same treatment:

curl -sI https://example.com/llms.txt | head -n 1        # 200 means something already serves it
curl -s https://example.com/robots.txt | grep -i '^sitemap'
Enter fullscreen mode Exit fullscreen mode

Two Sitemap: lines pointing at different indexes is noisy but rarely harmful. Two blocks declaring WebSite with different @id values is the case worth fixing.

What real duplication looks like

  • Same @type, different @id: two entities where there should be one. Remove one source.
  • Same @type, same @id, conflicting properties (telephone, address, price): worse, because one of the values is now simply wrong and you can't tell which from the outside.
  • A BreadcrumbList that doesn't match the visible breadcrumb: a rendering problem that also leaks into search results.
  • An Article node whose dateModified no human edited.

A node that only one source produces is not duplication, even if the plugin you already run is supposed to cover that type. Configuration differs per site; the HTML is the ground truth, not the feature list.

Where a second plugin earns its place

The type you need isn't modelled. A full-suite SEO plugin covers the common vocabulary — organization, website, article, breadcrumb, sometimes product — and typically has no field for Service with areaServed, no way to express product variants as separate Offer nodes, and nothing that ties an FAQPage to the questions actually visible on the page. If your pages describe services by region, that gap is the reason to add something.

You need per-page control. One graph for every URL of a post type is convenient until two pages need different structures. A plugin that lets you edit the node for a single page solves that without touching the rest.

Nothing serves llms.txt. It is a proposed convention rather than a ratified standard, and no major crawler documents that it reads one, so the effect is unproven. Publishing it costs nothing and conflicts with nothing as long as it is the only producer.

Where it just duplicates

If the new plugin emits Organization, WebSite, BreadcrumbList and Article on the same URLs the suite already covers, you are adding weight and a second set of facts. Meta descriptions and XML sitemaps behave the same way: two plugins writing a description tag means one wins per page by hook priority, and nobody documented which.

A rough map — verify against your own output rather than trusting the middle column:

Need Full-suite SEO plugin Extra plugin justified?
Title, meta description, canonical yes no
XML sitemap yes no
Organization / WebSite usually no
BreadcrumbList usually no
Article basics yes no
Service with areaServed rarely yes
Product variants as separate offers partial maybe
Per-page node editing limited yes
llms.txt usually not yes, if nothing else serves it

If you add one, remove the overlap first

  1. Re-run the scanner and save the output.
  2. Switch off the overlapping module in one of the two. Most full-suite plugins let you disable their schema output; one that doesn't is a reason to prefer a different arrangement.
  3. Re-run the scanner. The node count should drop and nothing you need should disappear.
  4. Install the new plugin, re-run the scanner, compare the list of @type values rather than the counts.

One plugin that works this shape is AumViso: it emits llms.txt, structured data, a sitemap, meta tags and internal links. If your suite already produces the sitemap and the meta tags, the honest reason to install it is the structured data and the llms.txt, and you would turn the suite's schema module off in the same session.

The scanner is the whole test. Same URL, same script, before and after. If the second run shows the same @type list with more nodes, the plugin added nothing; if it shows a type that wasn't there before, it did its job.

Top comments (0)