• I moved from Yoast because of its aggressive freemium model — too many features locked behind a paywall, too much noise in the interface. The SEO Framework looked like a clean, honest alternative, and for the core plugin it is.

    I installed the Extension Manager following the official recommendation to import data from Yoast. It also installs Troy Client — a component that cannot be removed through the WordPress admin. Not by deactivating and removing the Extension Manager. Not by disabling The SEO Framework entirely. The only way to get rid of it is via FTP or file manager.

    To make things worse, most of what the free extensions offer can be achieved with a few lines of code in a mu-plugin. Redirecting attachment pages to their parent post, hiding debug comments from the HTML output, fixing title output — these are all trivial to implement without installing anything extra. Pushing users toward an extension ecosystem for things that don’t require one raises questions about the real purpose of that ecosystem.

    This is exactly the kind of silent lock-in I was trying to escape. Shipping a component that survives the removal of the plugin that installed it is not acceptable for a project that presents itself as lightweight and transparent.

    Also, there is an issue with breadcrumbs:

    <nav aria-label="Breadcrumb" class="tsf-breadcrumb">
    <ol>
    <li class="breadcrumb-item"><a href="https://salvatorenoschese.it/">Home</a></li>
    <li class="breadcrumb-item"><a href="https://salvatorenoschese.it/category/linux/">Categoria: Linux</a></li>
    </ol>
    </nav>
    <style>nav.tsf-breadcrumb ol{display:inline;list-style:none;margin-inline-start:0}nav.tsf-breadcrumb ol li{display:inline}nav.tsf-breadcrumb ol li:not(:last-child)::after{content:'\203A';margin-inline-end:1ch;margin-inline-start:1ch}</style>

    Style is added inside body and after it’s element.

    • This topic was modified 2 months, 2 weeks ago by Salvatore Noschese. Reason: + CSS issue
    • This topic was modified 2 months, 2 weeks ago by Salvatore Noschese. Reason: one more ⭐
Viewing 2 replies - 1 through 2 (of 2 total)
  • Plugin Author Sybre Waaijer

    (@cybr)

    Hi Salvatore,

    You’re right that this should be clearer — thanks for the feedback!

    Troy Client blocks its own deactivation while plugins that depend on it are still installed. Troy Client delivers plugin updates through Troy — an independent update system I built last year. A number of my plugins already depend on it. The lock icon on the Plugins screen lists exactly which plugins are holding it in place — once those are removed, Troy Client unlocks and can be deleted.

    That said, the “why” wasn’t easy to find. Troy is a new project, and I’m still working on making this stuff obvious. Two things changed since your review:

    If you can replace extension functionality with a few lines in a mu-plugin (that’s advanced!), you’re probably not the target audience for the extensions — and that’s fine. Everything is open source. The core plugin is already a fully fledged SEO suite, and it sounds like that’s all you need.

    I appreciate you taking the time to write this up. This is exactly the kind of feedback that helps me improve. Thank you!

    As for the breadcrumbs: the inline style in the body is valid HTML5 and renders correctly. If you’d like to customize the breadcrumb styling, feel free to open a support thread and I’ll gladly help you sort it out.

    Thread Starter Salvatore Noschese

    (@salvatorenoschese)

    Thank you for the detailed response — the dedicated page and the upcoming link in the plugin row are genuinely good improvements.

    Two things I’d still push back on:

    **Troy Client and removal.** The SEO Framework itself installs and updates perfectly fine through WordPress.org without Troy. Troy Client only enters the picture if you install the Extension Manager — which many users will do just to try the free extensions or to run the Yoast import. If they later decide the extensions aren’t for them and remove the Extension Manager, they’d reasonably expect the slate to be clean. Leaving a component behind that can only be removed via FTP isn’t a UX edge case — it’s a trust issue, especially for a plugin that markets itself on being lightweight and non-intrusive.

    **The inline <style> in the body.** You mention it’s valid HTML5, and technically that’s true — the spec does permit <style> outside <head>. In practice though, it’s still considered poor form: it blocks rendering, it can interfere with CSP headers, and it places presentation logic where content is expected. Injecting it via wp_head would be the standard WordPress approach and would sidestep all of those concerns.

    about code fix i used this:

    <?php
    /**
    * Plugin Name: Fix SEO Framework
    * Description: Removes inline CSS and indicators from The SEO Framework, adds breadcrumb styles on singular posts only, redirects attachments to their parent page, and hides the Extensions button in the admin.
    * Author: Salvatore Noschese
    */

    // Remove the "Generated by The SEO Framework" indicator from the HTML source
    add_filter( 'the_seo_framework_indicator', '__return_false' );

    // Remove inline <style> block and current-page breadcrumb item from the breadcrumb output
    add_filter( 'the_seo_framework_breadcrumb_shortcode_output', function( $html, $atts ) {
    $patterns = [
    '/<style\b[^>]*>.*?<\/style>/is',
    '/<li[^>]*>(?:(?!<li).)*aria-current[^<]*<\/span>\s*<\/li>/s'
    ];
    return preg_replace( $patterns, '', $html );
    }, 10, 2 );

    // Redirect attachment pages to their parent post, or to the homepage if orphaned
    add_action( 'template_redirect', function() {
    if ( is_attachment() ) {
    $parent_id = get_post()->post_parent;
    $redirect = $parent_id ? get_permalink( $parent_id ) : home_url( '/' );
    wp_redirect( $redirect, 301 );
    exit;
    }
    } );

    // Output breadcrumb styles in the <head> on singular posts/pages only
    add_action( 'wp_head', function() {
    if ( is_singular() ) {
    echo <<<CSS
    <style>
    nav.tsf-breadcrumb ol {
    display: inline;
    list-style: none;
    margin-inline-start: 0;
    padding: 0;
    }
    nav.tsf-breadcrumb ol li {
    display: inline;
    }
    nav.tsf-breadcrumb ol li a {
    text-decoration: none;
    }
    nav.tsf-breadcrumb ol li:not(:last-child)::after {
    content: '\\00BB';
    margin-inline-end: 1ch;
    margin-inline-start: 1ch;
    }
    </style>\n
    CSS;
    }
    } );

    best regards 😊

Viewing 2 replies - 1 through 2 (of 2 total)

You must be logged in to reply to this review.