What XenForo 2.3+ Should Build Next for “Web 3.0”-Ready SEO

Roiarthur

Active member
Search today rewards three things: structured understanding (schema/knowledge graphs), fast and stable UX (Core Web Vitals), and authentic conversations (forums, Q&A, expert authors). Below is a practical roadmap for XenForo 2.3+ to lead on all three.

1) Ship complete, context-aware structured data

Thread & post views


Output DiscussionForumPosting JSON-LD for discussion threads, including author, datePublished, commentCount, interactionStatistic (upvotes/likes), and canonical URL per page.

For Question forums, emit QAPage only when the thread is a real Q&A (and toggle it off if the thread is converted back to a general discussion). Map XenForo’s “Best answer” to acceptedAnswer. Validate against Google’s QAPage guidance.
Google for Developers

Profiles
On member_view, publish ProfilePage data with sameAs (links to the member’s verified sites), and optional knowsAbout (tags/areas of expertise surfaced from activity). This supports E-E-A-T signals in a standards-based way. (See Google’s structured-data guidelines hub for ongoing changes.)
Google for Developers

Resources (XFRM)
Provide schema presets per resource type: SoftwareApplication, HowTo, Book, etc., selectable in the ACP and inserted automatically into xfrm_resource_view. Reference Google’s rich-result docs (FAQ/HowTo/etc.) for valid properties.
Google for Developers

Global
Put site-wide Organization, Website (with sitelinks searchbox), and BreadcrumbList in PAGE_CONTAINER. Expose toggles per style/add-on and a live JSON-LD preview with Rich Results Test links.
Google for Developers


2) Make discovery & recrawl instantaneous

Sitemaps that reflect posts, not only threads

Generate a post-freshness sitemap where lastmod tracks the newest reply, improving recrawl of active discussions.

IndexNow in core (optional toggle)
When threads or posts are created/edited/soft-deleted, queue URL pings to IndexNow with retry/backoff and a dashboard. Provide key management in ACP.
IndexNow

Robots hygiene
Auto-noindex thin combinations (deep pagination + filters), enforce clean canonicals per page, and mark outbound UGC links rel="ugc" by default, with role-based exceptions for trusted members.
Google for Developers


3) Treat Core Web Vitals as first-class product metrics

INP-first engineering

Since INP replaced FID as a Core Web Vital, expose an ACP “Performance Budget” with guardrails: defer non-critical JS from add-ons, pre-allocate image heights (to prevent CLS), and limit long task handlers in thread lists and message editors. Provide developer hooks to register deferred scripts.
web.dev

Built-in asset hints
Automatic preconnect/preload for critical fonts, editor assets, and CDNs; lazy-load media with proper placeholders; responsive image srcsets for attachments and galleries.


4) Align with Search’s growing emphasis on forums & Q&A

Eligibility for forum-specific surfacing

Ensure public access to discussions (no interstitials on first view), strong internal linking (latest/related threads), and clear author attributions. These are consistent with Google’s trend of surfacing forum discussions and Q&A more prominently.
pageonepower.com

Search Console integration (optional add-on)
Pull GSC data via API into ACP panels filtered by thread type (Q&A vs discussion) to measure impressions/clicks for structured-result appearances and forum-specific surfaces. (Track changes with Google’s docs updates log.)
Google for Developers


5) Operationalize author trust (E-E-A-T)

Verified contributor profiles


Add ACP options for expert badges, affiliations, and identity verification. Expose a “Top answers” list per author (ItemList JSON-LD) and link from the profile. This rewards helpful contributors and clarifies expertise for search engines and users.

Reputation-aware link policy
Start with rel="ugc" forum-wide, but allow per-group rules to remove it automatically for consistently high-quality contributors, as Google permits.
Google for Developers


6) Internationalization, accessibility, and cleanliness

Auto-emit hreflang for multilingual communities and language-scoped forums.

Ensure pagination canonicals are correct and that infinite-scroll views still expose crawlable URLs.
Tighten ARIA roles and keyboard support in editor/thread lists; better accessibility helps INP and overall usability.


7) Developer & theming ergonomics

JSON-LD builder

A visual mapper in ACP that lets admins connect XenForo fields (thread title, best answer, like counts, tags) to schema fields, with type presets (Discussion/QAPage/HowTo/SoftwareApplication) and validation links.

Performance hooks
Documented events for: deferring add-on JS, batching intersection observers, image hydration strategies, and scheduling low-priority tasks after user input to protect INP.

Implementation checklist by template

PAGE_CONTAINER: Organization, Website, BreadcrumbList; preload/preconnect hints; lazy-loading defaults.

forum_view / thread_view: DiscussionForumPosting; switch to QAPage in Question forums; correct canonicals per page; UGC link policy.
Google for Developers

member_view: ProfilePage with sameAs, optional knowsAbout, and an ItemList of accepted answers.

xfrm_resource_view: Schema preset matching the resource type; optional FAQ blocks when appropriate.
Google for Developers

Why this matters

Faster recrawls and richer snippets from accurate schema + indexNow.

Better rankings stability by meeting today’s INP responsiveness bar.
web.dev

More visibility where search is steering users: real people discussing real problems in forums and Q&A.
pageonepower.com

If you’d like, I can deliver this as a practical bundle: JSON-LD macros for thread_view, member_view, and xfrm_resource_view, plus a lightweight IndexNow add-on with ACP controls and a minimal performance-budget overlay.
 
What XenForo 2.3+ Should Build Next for Web-3.0-Ready SEO

Search in 2025 favors three things: structured understanding of pages, fast and stable interaction, and authentic human conversations. Forums are back in the spotlight—if they speak search engines’ language and load fast. Here’s a focused roadmap for XenForo 2.3+ to lead.


1) Ship complete, context-aware structured data (JSON-LD)

Discussion threads & posts


Emit Discussion Forum structured data on thread pages so Google can identify forum content and consider it for “Discussions & forums” and Perspectives surfaces. Include rich author objects, post counts, and interaction stats. This improves eligibility it’s not a guarantee.
Google for Developers

Question forums (true Q&A)

When a thread is a real question with answers, switch to QAPage. Map XenForo’s “Best answer” to acceptedAnswer, keep all other replies under suggestedAnswer, and fall back to discussion markup if the thread is converted back to general discussion. Validate with Google’s Rich Results Test.
Google for Developers

Member profiles

On member_view, publish Profile Page structured data: name, links (sameAs), and activity signals (for expertise context). This helps search better understand who is speaking in your community.
Google for Developers

Resource Manager

Provide schema presets per resource type (for example, HowTo, SoftwareApplication) and inject them into xfrm_resource_view only when properties are complete and compliant.
Google for Developers

Minimal example for a Question thread (server-side toggle to QAPage only in Question forums):

Code:
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "QAPage",
  "mainEntity": {
    "@type": "Question",
    "name": "How do I fix [problem] in XenForo?",
    "datePublished": "2025-10-10T12:34:00Z",
    "author": {"@type": "Person", "name": "Alice"},
    "acceptedAnswer": {
      "@type": "Answer",
      "text": "Step 1… Step 2… with references.",
      "datePublished": "2025-10-11T09:12:00Z",
      "author": {"@type": "Person", "name": "Bob"},
      "upvoteCount": 23
    },
    "suggestedAnswer": [
      {"@type": "Answer", "text": "Alternative…", "author": {"@type": "Person", "name": "Carol"}}
    ]
  }
}
</script>

(Use Discussion Forum markup on standard discussions; don’t mix both on the same page.)
Google for Developers


2) Make discovery and re-crawl instantaneous

IndexNow: Add a core (or official add-on) implementation that pings participating engines on thread/post create, edit, move, and delete. Include a retry queue, key management, and a small ACP dashboard.
IndexNow

Sitemaps that reflect replies: Provide a “recent posts” sitemap whose lastmod follows the most recent reply, not only the thread creation time. (This increases the chance of fresh re-crawls on active topics.)

3) Treat Core Web Vitals as product requirements (INP-first)

Google replaced FID with INP (Interaction to Next Paint) on March 12, 2024. XenForo should expose an ACP “Performance Budget” and developer hooks to keep INP healthy: defer non-critical JS, pre-allocate media slots to prevent layout shift, batch long tasks, and cap heavy observers on long thread lists.
web.dev

Practical additions:

Async/deferred loading for add-on scripts with a platform-level queue.

Automatic loading="lazy" and responsive srcset for attachments and embedded media.

Hints (preconnect, preload) for critical editor/font assets.


4) Link hygiene for UGC (and rewarding trusted members)

Default outbound links in posts and signatures to rel="ugc". Allow role-based exceptions that remove ugc for consistently high-quality contributorsexactly as Google recommends for communities. Provide an ACP report tracking where exceptions are applied.
Google for Developers


5) Internationalization, accessibility, and canonical clarity

Emit clean canonicals per pagination page and make infinite scroll expose crawlable URLs.

Generate hreflang across multilingual forums and language-scoped sections to reduce duplication.

Tighten ARIA roles and keyboard support (good for users and often correlated with better INP).

Note: Google deprecated the Sitelinks Search Box visual feature in 2024; keep Organization/site name markup, but don’t rely on the old box.
Google for Developers

Search Engine Journal


6) Admin tools that make SEO operational


JSON-LD Builder (ACP): map XenForo fields (thread title, best answer, like counts, tags) to schema properties with presets for Discussion/QAPage/HowTo/SoftwareApplication; include a live preview and links to the Rich Results Test.
Google for Developers

Search Console panel (official add-on): pull impressions/clicks for threads and Q&A, segment by forum type, and highlight crawling/indexing issues affecting discussion and profile markup. (Monitor Google’s docs updates log to keep labels current.)
Google for Developers


7) Implementation checklist by template

PAGE_CONTAINER
: Organization/site name, breadcrumbs, critical asset hints.

forum_view / thread_view: Discussion Forum markup by default; switch to QAPage only in Question forums and when a best answer exists; strict canonicals per page; outbound link policy (rel="ugc").
Google for Developers

member_view: Profile Page markup with sameAs for verified links and optional expertise fields.
Google for Developers

xfrm_resource_view
: Correct type preset (for example, HowTo, SoftwareApplication) with required properties only when complete.
Google for Developers

Bottom line

Forums win in modern search when they’re understandable (clean schema for threads, questions, and people), discoverable (IndexNow + freshness-aware sitemaps), and delightfully responsive (INP-first). If XenForo bakes these into 2.3+ as core plus an official “SEO+” add-on, communities will be future-proofed for where search is heading.
 
Here’s a ready-to-install starter bundle you can drop into XenForo 2.3+. It’s split in two parts:

JSON-LD template macros you can include in thread_view, member_view, and (optionally) xfrm_resource_view.

A minimal IndexNow add-on skeleton (PHP) that pings on thread/post create/update/delete, with simple ACP options.

All code is in English as requested.


1) JSON-LD macros (single template)

Create a new template (admin → Appearance → Templates) named:

Code:
seo_jsonld_macros

Paste this content:

Code:
<xf:macro name="qapage" arg-thread="!" arg-bestAnswer="!" arg-suggestedAnswers="!">
    <xf:set var="$schema">
        {
            "@context": "https://schema.org",
            "@type": "QAPage",
            "mainEntity": {
                "@type": "Question",
                "name": "{$thread.title|escape('json')}",
                "text": "{$thread.first_post.messagePlain|stripTags|truncate(400)|escape('json')}",
                "author": {
                    "@type": "Person",
                    "name": "{$thread.User.username|escape('json')}"
                },
                "datePublished": "{$thread.post_date|date('c')}",
                "dateModified": "{$thread.last_post_date|date('c')}",
                "acceptedAnswer": {
                    "@type": "Answer",
                    "text": "{$bestAnswer.messagePlain|stripTags|escape('json')}",
                    "datePublished": "{$bestAnswer.post_date|date('c')}",
                    "author": {
                        "@type": "Person",
                        "name": "{$bestAnswer.User.username|escape('json')}"
                    },
                    "upvoteCount": {$bestAnswer.reaction_score|default(0)}
                },
                "suggestedAnswer": [
                    <xf:foreach loop="$suggestedAnswers" value="$ans" i="$i">
                        {
                            "@type": "Answer",
                            "text": "{$ans.messagePlain|stripTags|escape('json')}",
                            "datePublished": "{$ans.post_date|date('c')}",
                            "author": {
                                "@type": "Person",
                                "name": "{$ans.User.username|escape('json')}"
                            },
                            "upvoteCount": {$ans.reaction_score|default(0)}
                        }<xf:if is="$i < count($suggestedAnswers) - 1">,</xf:if>
                    </xf:foreach>
                ]
            }
        }
    </xf:set>

    <script type="application/ld+json"><xf:raw>{{ json($schema) }}</xf:raw></script>
</xf:macro>

<xf:macro name="discussion" arg-thread="!" arg-posts="!">
    <xf:set var="$interactionCount">{$thread.first_post.reaction_score|default(0) + $thread.reply_count|default(0)}</xf:set>
    <xf:set var="$schema">
        {
            "@context": "https://schema.org",
            "@type": "DiscussionForumPosting",
            "headline": "{$thread.title|escape('json')}",
            "articleBody": "{$thread.first_post.messagePlain|stripTags|escape('json')}",
            "datePublished": "{$thread.post_date|date('c')}",
            "dateModified": "{$thread.last_post_date|date('c')}",
            "author": { "@type": "Person", "name": "{$thread.User.username|escape('json')}" },
            "interactionStatistic": {
                "@type": "InteractionCounter",
                "interactionType": "https://schema.org/LikeAction",
                "userInteractionCount": {$interactionCount|default(0)}
            },
            "commentCount": {$thread.reply_count|default(0)},
            "comment": [
                <xf:foreach loop="$posts" value="$p" i="$i">
                    {
                        "@type": "Comment",
                        "text": "{$p.messagePlain|stripTags|escape('json')}",
                        "datePublished": "{$p.post_date|date('c')}",
                        "author": { "@type": "Person", "name": "{$p.User.username|escape('json')}" }
                    }<xf:if is="$i < count($posts) - 1">,</xf:if>
                </xf:foreach>
            ],
            "mainEntityOfPage": "{$thread.canonicalUrl()|escape('json')}"
        }
    </xf:set>

    <script type="application/ld+json"><xf:raw>{{ json($schema) }}</xf:raw></script>
</xf:macro>

<xf:macro name="profilePage" arg-user="!">
    <xf:set var="$sameAs">
        <xf:if is="$user.Profile.website">{"url":"{$user.Profile.website|escape('json')}"}<xf:elseif is="$user.Profile" />
        </xf:if>
    </xf:set>

    <xf:set var="$schema">
        {
            "@context": "https://schema.org",
            "@type": "ProfilePage",
            "name": "{$user.username|escape('json')}",
            "dateModified": "{$user.last_activity|date('c')}",
            "mainEntity": {
                "@type": "Person",
                "name": "{$user.username|escape('json')}",
                "image": "<xf:avatar user=\"$user\" size=\"m\" canonical=\"1\" />",
                <xf:if is="$user.Profile.about">"description": "{$user.Profile.about|stripTags|truncate(400)|escape('json')}",</xf:if>
                <xf:if is="$user.Profile.website">"sameAs": ["{$user.Profile.website|escape('json')}"],</xf:if>
                "interactionStatistic": {
                    "@type": "InteractionCounter",
                    "interactionType": "https://schema.org/CommentAction",
                    "userInteractionCount": {$user.message_count|default(0)}
                }
            }
        }
    </xf:set>

    <script type="application/ld+json"><xf:raw>{{ json($schema) }}</xf:raw></script>
</xf:macro>

<xf:macro name="howto" arg-resource="!" arg-steps="!">
    <xf:set var="$schema">
        {
            "@context":"https://schema.org",
            "@type":"HowTo",
            "name":"{$resource.title|escape('json')}",
            "description":"{$resource.tag_line|default($resource.description)|stripTags|truncate(300)|escape('json')}",
            "datePublished":"{$resource.create_date|date('c')}",
            "step":[
                <xf:foreach loop="$steps" value="$s" i="$i">
                { "@type":"HowToStep", "name":"{$s.title|escape('json')}", "text":"{$s.text|stripTags|escape('json')}" }<xf:if is="$i < count($steps) - 1">,</xf:if>
                </xf:foreach>
            ]
        }
    </xf:set>
    <script type="application/ld+json"><xf:raw>{{ json($schema) }}</xf:raw></script>
</xf:macro>

<xf:macro name="softwareApp" arg-resource="!">
    <xf:set var="$schema">
        {
            "@context":"https://schema.org",
            "@type":"SoftwareApplication",
            "name":"{$resource.title|escape('json')}",
            "applicationCategory":"Utilities",
            "operatingSystem":"Windows, macOS, Linux",
            "offers": { "@type":"Offer", "price":"0", "priceCurrency":"USD" },
            "aggregateRating": {
                "@type":"AggregateRating",
                "ratingValue":"{$resource.rating_avg|default(5)}",
                "reviewCount":"{$resource.rating_count|default(1)}"
            }
        }
    </xf:set>
    <script type="application/ld+json"><xf:raw>{{ json($schema) }}</xf:raw></script>
</xf:macro>

Where to include them

thread_view
(at the end of the template, once per page):

Code:
<xf:if is="$thread.type_id == 'question' && $thread.best_answer_id">

    <xf:macro template="seo_jsonld_macros" name="qapage"

        arg-thread="{$thread}"

        arg-bestAnswer="{$posts.{$thread.best_answer_id}}"

        arg-suggestedAnswers="{{ array_values($posts) }}" />

<xf:else />

    <xf:macro template="seo_jsonld_macros" name="discussion"

        arg-thread="{$thread}"

        arg-posts="{{ array_values($posts) }}" />

</xf:if>

member_view:

Code:
<xf:macro template="seo_jsonld_macros" name="profilePage" arg-user="{$user}" />

xfrm_resource_view (optional; pick one preset based on category/fields):

Code:
<xf:if is="$resource.custom_fields.howto_steps">
    <xf:macro template="seo_jsonld_macros" name="howto"
        arg-resource="{$resource}"
        arg-steps="{$resource.custom_fields.howto_steps}" />
<xf:elseif is="$resource.Resource.category_id == 1" />
    <xf:macro template="seo_jsonld_macros" name="softwareApp" arg-resource="{$resource}" />
</xf:if>

Notes

We deliberately don’t mix QAPage and DiscussionForumPosting on the same page.

messagePlain ensures we don’t inject HTML into JSON-LD.

json() + <xf:raw> prevents double-escaping inside <script type="application/ld+json">.


2) Minimal IndexNow add-on (skeleton)

Namespace: SD/IndexNow (change to your vendor prefix).

Create a new add-on from the Dev tools (or by hand). File structure:

Code:
src/addons/SD/IndexNow/addon.json
src/addons/SD/IndexNow/Setup.php
src/addons/SD/IndexNow/Listener/EntityHooks.php
src/addons/SD/IndexNow/Service/Pinger.php
src/addons/SD/IndexNow/Option/Options.php

addon.json

Code:
{
  "title": "IndexNow Pinger",
  "description": "Pings IndexNow on thread/post create/update/delete with retry.",
  "version_id": 1000070,
  "version_string": "1.0.0",
  "dev": "SD",
  "namespace": "SD\\IndexNow",
  "require": [],
  "icon": "fa-solid fa-sitemap"
}

Setup.php (register a simple option group)

Code:
<?php

namespace SD\IndexNow;

use XF\AddOn\AbstractSetup;
use XF\Db\Schema\Create;

class Setup extends AbstractSetup
{
    public function install(array $stepParams = [])
    {
        $this->schemaManager()->createTable('xf_sd_indexnow_queue', function (Create $table) {
            $table->addColumn('queue_id', 'int')->autoIncrement();
            $table->addColumn('url', 'varchar', 2083);
            $table->addColumn('action', 'varchar', 16)->setDefault('urlUpdated');
            $table->addColumn('attempts', 'int')->setDefault(0);
            $table->addColumn('next_attempt', 'int')->setDefault(0);
            $table->addPrimaryKey('queue_id');
            $table->addKey('next_attempt');
        });
    }

    public function upgrade(array $stepParams = [])
    {
    }

    public function uninstall(array $stepParams = [])
    {
        $this->schemaManager()->dropTable('xf_sd_indexnow_queue');
    }
}

Option/Options.php (ACP options provider)

Register two options in Admin → Setup → Options (create group sdIndexNow in the ACP, or ship _output/options if you prefer export). The handler:

Code:
<?php

namespace SD\IndexNow\Option;

class Options
{
    public static function isEnabled(\XF\App $app): bool
    {
        return (bool)$app->options()->sdIndexNow_enable;
    }

    public static function endpoint(\XF\App $app): string
    {
        return (string)$app->options()->sdIndexNow_endpoint ?: 'https://api.indexnow.org/indexnow';
    }

    public static function key(\XF\App $app): string
    {
        return (string)$app->options()->sdIndexNow_key;
    }

    public static function host(\XF\App $app): string
    {
        return (string)$app->options()->sdIndexNow_host ?: $app->options()->boardUrl;
    }
}

Create these ACP options:

sdIndexNow_enable
(checkbox)

sdIndexNow_key (text) — your IndexNow key

sdIndexNow_endpoint (text) — default https://api.indexnow.org/indexnow

sdIndexNow_host (text) — default to your board URL’s host

Service/Pinger.php

Code:
<?php

namespace SD\IndexNow\Service;

use SD\IndexNow\Option\Options;
use XF\App;
use XF\Mvc\Entity\Entity;

class Pinger
{
    protected App $app;

    public function __construct(App $app)
    {
        $this->app = $app;
    }

    public function queue(string $url, string $action = 'urlUpdated'): void
    {
        if (!Options::isEnabled($this->app)) { return; }

        $db = $this->app->db();
        $db->insert('xf_sd_indexnow_queue', [
            'url' => $url,
            'action' => $action,
            'attempts' => 0,
            'next_attempt' => time()
        ]);
    }

    public function processQueue(int $batch = 20): int
    {
        $rows = $this->app->db()->fetchAll("
            SELECT * FROM xf_sd_indexnow_queue
            WHERE next_attempt <= ?
            ORDER BY queue_id ASC
            LIMIT {$batch}
        ", [time()]);

        $sent = 0;
        foreach ($rows as $row)
        {
            if ($this->send([$row['url']], $row['action']))
            {
                $this->app->db()->delete('xf_sd_indexnow_queue', 'queue_id = ?', $row['queue_id']);
                $sent++;
            }
            else
            {
                // backoff: +5m * attempts
                $this->app->db()->update('xf_sd_indexnow_queue', [
                    'attempts' => $row['attempts'] + 1,
                    'next_attempt' => time() + max(300, ($row['attempts'] + 1) * 300)
                ], 'queue_id = ?', $row['queue_id']);
            }
        }

        return $sent;
    }

    public function send(array $urls, string $action = 'urlUpdated'): bool
    {
        $key = Options::key($this->app);
        $endpoint = Options::endpoint($this->app);
        $host = parse_url(Options::host($this->app), PHP_URL_HOST) ?: $_SERVER['HTTP_HOST'];

        if (!$key || !$endpoint || !$host || empty($urls)) { return false; }

        $payload = [
            'host' => $host,
            'key' => $key,
            'keyLocation' => "https://{$host}/{$key}.txt",
            $action => $urls
        ];

        try
        {
            $client = $this->app->http()->client();
            $response = $client->post($endpoint, [
                'headers' => ['Content-Type' => 'application/json'],
                'json' => $payload,
                'timeout' => 5
            ]);
            return ($response->getStatusCode() >= 200 && $response->getStatusCode() < 300);
        }
        catch (\Throwable $e)
        {
            \XF::logError('[IndexNow] ' . $e->getMessage());
            return false;
        }
    }
}
 
Listener/EntityHooks.php
Code:
<?php

namespace SD\IndexNow\Listener;

use SD\IndexNow\Service\Pinger;
use XF\Mvc\Entity\Entity;

class EntityHooks
{
    // Called from code event listeners (configure in Admin > Development > Code event listeners)
    public static function entityPostSave(Entity $entity)
    {
        $app = \XF::app();
        $pinger = new Pinger($app);

        if ($entity instanceof \XF\Entity\Thread)
        {
            $pinger->queue($entity->getContentUrl(true), 'urlUpdated');
        }
        elseif ($entity instanceof \XF\Entity\Post)
        {
            // Post canonical may be page-specific; safest is the parent thread canonical
            $thread = $entity->Thread;
            if ($thread)
            {
                $pinger->queue($thread->getContentUrl(true), 'urlUpdated');
            }
        }
        elseif (class_exists('\XFRM\Entity\ResourceItem') && $entity instanceof \XFRM\Entity\ResourceItem)
        {
            $pinger->queue($entity->getContentUrl(true), 'urlUpdated');
        }
    }

    public static function entityPostDelete(Entity $entity)
    {
        $app = \XF::app();
        $pinger = new Pinger($app);

        if (method_exists($entity, 'getContentUrl'))
        {
            $pinger->queue($entity->getContentUrl(true), 'urlDeleted');
        }
    }
}

Register the listeners (Dev → Code event listeners):

Event: entity_post_save → SD\IndexNow\Listener\EntityHooks::entityPostSave

Event: entity_post_delete → SD\IndexNow\Listener\EntityHooks::entityPostDelete

Cron to process the queue (Dev → Cron entries):

Run every 5 minutes, callback:
Code:
SD\IndexNow:Cron::process/CODE]

Create the cron runner:

[CODE]src/addons/SD/IndexNow/Cron.php

Code:
<?php

namespace SD\IndexNow;

use SD\IndexNow\Service\Pinger;

class Cron
{
    public static function process()
    {
        $app = \XF::app();
        /** @var Pinger $pinger */
        $pinger = $app->service('SD\IndexNow:Service\Pinger');
        if (!$pinger) { $pinger = new Service\Pinger($app); }
        $pinger->processQueue(50);
    }
}

Quick integration checklist

Templates


Add seo_jsonld_macros template.

Include the macro calls in thread_view, member_view, and xfrm_resource_view as shown.

Clear caches and test with Google’s Rich Results Test.

IndexNow add-on

Install the add-on (zip the src/addons/SD/IndexNow folder + addon.json, or use Dev tools export).

In ACP → Options → IndexNow, enable it, set your key, ensure you host the https://yourhost/{key}.txt file as required by IndexNow.

Create the two listeners and the cron entry.

Post a test thread and check server logs / IndexNow dashboard.
 
The New Generation of AI Browsers: From “Assistant in a Tab” to True Web Agents

AI is changing the browser from a passive viewer into an active partner. The latest crop of “AI-first” browsers ships with built-in assistants that can summarize, compare, plan, and more ambitiously carry out multi-step tasks on the web. Below is a practical tour of the space.


Flagship AI-first browsers


Comet (Perplexity) A personal AI assistant wrapped in a browser. Comet blends Perplexity’s real-time research with page-aware help, so you can ask for comparisons, shopping assistance, itinerary planning, or quick syntheses without bouncing between tabs. Public downloads are available, and access has broadened since the initial limited launch.

ChatGPT Atlas (OpenAI) A browser built with ChatGPT at its core. Atlas puts ChatGPT directly into the browsing workflow: get instant answers and summaries from any page, use a sidebar to rewrite or compare content, and tap early “agentic” flows for tasks like trip research or shopping. Initial availability is focused on macOS.

Dia (The Browser Company) Think “chat with your tabs.” Dia treats every open page as context you can talk to—summarize a PDF, extract steps from a tutorial, or plan from multiple sources in one thread. It succeeds Arc as the company’s AI-centric priority.

SigmaOS (A1Kit + Airis) Positions itself as an “AI browser engine.” SigmaOS uses agents plus custom page extraction so the assistant understands what you’re working on and can help explain, summarize, and act within that context. Airis is the native companion.

Sigma AI Browser (independent project) An “agentic” take aimed at automating workflows (booking, planning, deep research) directly in the browser surface.

Arc Search (mobile) Not a desktop workhorse, but its “Browse for Me” shows where things are headed on phones: one synthesized answer that compiles instant answers, recommendations, comparisons, and step-by-step guides.

Mainstream browsers with deep AI integration

Opera (Opera One / GX) with Aria Free, built-in assistant with real-time web access, quick sidebar/command-line entry, image generation, and tab-level “agentic” control.

Brave with Leo Privacy-forward assistant integrated into the browser. Leo can summarize pages and videos, answer questions with Brave Search context, translate/rewrite, and is steadily adding multi-modal and “agentic” capabilities.


What these browsers actually do (and why it matters)

Understand the page, not just the query.
Instead of sending generic prompts to a chatbot, these browsers pass page context to the model. That enables ask-and-act flows like “Compare these three products,” “Extract the steps,” or “Draft a polite reply based on this page.” Comet, Atlas, Dia, and SigmaOS all emphasize page-aware help.

Summarize and synthesize across tabs. The assistant can pull from multiple sources and return a single, sourced brief cutting down on manual tab-shuffling. Comet, Atlas, and Arc Search make this a core pitch.

Move from “assist” to agents. Early agentic features attempt multi-step goals (plan a trip, book, track prices, draft and refine, etc.). It’s still cautious good assistants explain before they act but this is the frontier everyone is racing toward. Opera calls out “agentic AI,” SigmaOS speaks of agents in the engine, and Atlas/Comet signal similar ambitions.

Tight privacy postures are differentiators. Brave, notably, details how Leo handles context and search without retaining personal identifiers. Privacy will increasingly separate serious tools from gimmicks.


Choosing your AI browser

Research & synthesis:
Comet and Atlas feel purpose-built for turning scattered pages into concise answers or plans.

Conversational workflows with many tabs: Dia shines when you want to talk to your workspace (“these tabs”) rather than to a single page.

Automation mindset: SigmaOS and Sigma AI Browser lean into agents and page extraction for semi-automated work.

Built-in to a familiar browser: Opera’s Aria and Brave’s Leo bring credible assistants to mainstream stacks.
 
Andy release the addon

 
🧠 The new wave of AI browsers: from “smart tab” to agents

📚 Introduction

Simply put, the browser is no longer a passive page viewer—it’s becoming a partner that understands the page, summarizes, compares, plans, and even kicks off multi-step tasks. This informational tutorial gives you a concrete tour of the main players and helps you choose based on your needs.



👤 Who is this for

This guide is for you if you want to stop tab-juggling, get sourced answers directly from the page you’re on, automate common tasks (travel, shopping, research), or try agents that can chain several steps.



🧰 Tools or prerequisites

• A recent macOS or Windows machine (availability varies by browser)

• An account to enable AI features (Perplexity, OpenAI, Opera, Brave, etc.)

• A stable Internet connection (real-time AI leans on the web)

• A bit of curiosity to configure privacy, context, and sources



🗺️ Overview

Step #1
> Understand the two families (AI-native vs. classic browsers with built-in AI)

Step #2 > Snapshot of flagship products and their positioning

Step #3 > What they actually deliver on pages and across tabs

Step #4 > How to choose based on concrete use cases



🧪 Difficulty level

Step #1
> Easy > conceptual landmarks to orient yourself

Step #2 > Easy > quick comparative reading

Step #3 > Easy > grasp page-awareness and multi-tab synthesis value

Step #4 > Medium > pick and tune for privacy and automation depth



🎯 Objectives

Understand the difference between an assistant “inside a browser” and a browser designed around AI

Identify relevant products (Comet, ChatGPT Atlas, Dia, SigmaOS, Opera Aria, Brave Leo)

Get the concrete benefits: page awareness, multi-tab synthesis, agents

Pick a tool matched to monitoring, extraction, planning, or automation



⚠️ Common pitfalls

• Confusing a sidebar assistant with an AI-native browser: integration depth and UX differ a lot

• Expecting the agent to “do everything alone”: good tools explain and ask before acting

• Skipping privacy setup (shared context, logs, identifiers) though it’s a true differentiator

• Forgetting agents still have limits for critical flows (payments, bookings): keep human oversight



🛠️ Step-by-step tutorial

🔹 Step #1 > Two families, two approaches

1️⃣ Main action

• Distinguish AI-native browsers (built around an agent, deep context, multi-step actions) from mainstream browsers with built-in AI (solid assistant, closer to classic browsing).

2️⃣ Why it matters

• This framing drives your choice: heavy exploration and automation → AI-native; smoother continuity → built-in AI.

3️⃣ Explanation

• AI-native: Comet (Perplexity), ChatGPT Atlas (OpenAI, macOS), Dia (The Browser Company), SigmaOS (A1Kit + Airis), Sigma AI Browser (independent).

• Built-in AI: Opera (Aria) and Brave (Leo) embed strong assistants in established browsers.



🔹 Step #2 > Product lineup (positioning at a glance)

1️⃣ Main action

• Scan each solution’s strengths to spot your functional “fit.”

2️⃣ Why it matters

• Promises look similar; priorities differ (real-time search, agents, privacy, mobility).

3️⃣ Explanation

Comet (Perplexity): real-time search + “page-aware” help for comparisons, shopping, planning, fast syntheses without leaving the tab.

ChatGPT Atlas (OpenAI): a browser centered on ChatGPT—instant overlays/summaries on any page, a sidebar to rewrite/compare, “agentic” starters; macOS rollout.

Dia (The Browser Company): “talk to your tabs,” summarize PDFs, extract steps, coordinate multiple pages in a single thread; the company’s AI priority after Arc.

SigmaOS (A1Kit + Airis): “AI browser engine” (agents + custom extraction); the assistant understands your current work context.

Sigma AI Browser (independent): agentic vision to automate flows (booking, planning, deep research).

Arc Search (mobile): “Browse for Me” → one synthetic answer on mobile (comparisons, how-tos).

Opera (Aria): free, integrated assistant (real-time web, image generation, tab-level agentic controls).

Brave (Leo): privacy-first assistant—page/video summaries, answers with Brave Search context, translation/rewrite, pushing toward multimodal/agentic features.



🔹 Step #3 > What they concretely bring

1️⃣ Main action

• Lean on page awareness and multi-tab synthesis to shrink cognitive load.

2️⃣ Why it matters

• You move from “doing research” to “getting an actionable brief” right from your pages.

3️⃣ Explanation

Page awareness: “Compare these 3 products,” “Extract the steps,” “Draft a polite reply from this page.” Comet, Atlas, Dia, SigmaOS shine here.

Multi-tab: one sourced brief across several pages—no tab gymnastics. Comet, Atlas, and Arc Search make it a pillar.

Agents: multi-step objectives (travel, booking, price tracking, iterative writing). Serious tools explain and seek approval before acting.

Privacy: Brave (Leo) documents context handling without persistent identifiers; expect privacy to matter more over time.



🔹 Step #4 > Choose by use case

1️⃣ Main action

• Align selection with specific scenarios, not a generic feature list.

2️⃣ Why it matters

• Each AI browser favors an angle: monitoring, conversational workspace, automation, or staying in your familiar ecosystem.

3️⃣ Explanation

Monitoring, synthesis, decisions: Comet, Atlas → turn scattered pages into clear action plans.

Many tabs, conversational work: Dia → “talk to your workspace.”

Automation/agents: SigmaOS, Sigma AI Browser → extraction + step chaining.

Stay in a familiar browser: Opera (Aria) and Brave (Leo) → robust assistants without switching ecosystems.



🪟 Differences across platforms

• macOS: early availability for some (e.g., ChatGPT Atlas, SigmaOS historically Mac-first), polished system integrations.

• Windows: availability ramping fast (Comet, Opera, Brave); some AI-native betas roll out in waves.

• Mobile: Arc Search exemplifies the “single synthetic answer” approach on smartphones.



💡 Pro tip

Create a “sandbox” profile: a secondary browser profile dedicated to AI. Test agents, summaries, and extractions without cluttering your main profile (bookmarks, cookies, extensions). When a workflow proves useful, migrate only the winners.



🚫 Do not

Don’t delegate sensitive operations (payments, signatures, personal data) to an agent without human review. Always demand a clear, verifiable recap before execution.



🧑‍💻 Professional advice

Define a data policy: which page contexts the AI may read, where content is processed, what traces are kept. On corporate machines, favor browsers offering fine-grained controls (logs, collection, secret redaction) and separate accounts for experimentation.



🔗 Further reading

Perplexity Comet (AI browser)

ChatGPT Atlas (OpenAI browser)

Dia Browser (The Browser Company)

SigmaOS (A1Kit + Airis)

Sigma AI Browser (independent)

Opera with Aria (integrated assistant)

Brave with Leo (privacy-first)

Recent analyses and comparisons (The Verge)

Market news & acquisitions (Reuters)



✅ Key takeaways

AI browsers move from passive viewing to guided action: page awareness, multi-tab synthesis, and agents. Choose based on your scenarios (monitoring, conversational workspace, automation, privacy) and keep human control for sensitive steps.
 
🧭 Why forums like XenForo must go native on LLMO JSON-LD (not an add-on)

📚 Introduction

Let’s be plain: AI overviews and assistants now “read the web” through structured data as much as through raw HTML. LLM Optimization (LLMO) with JSON-LD is how forums surface credible answers, preserve context, and earn attribution inside LLM responses. This cannot be a plug-in afterthought. To be effective, LLMO has to live in XenForo’s core, close to routing, permissions, canonical URLs, and content types.



👥 Who should read this

This guide is for forum owners, XenForo architects, and plugin authors who want threads, posts, resources, and user knowledge to appear as structured, attributed facts in AI search and assistants—reliably, at scale, and without schema drift.



🧰 Tools or prerequisites

• A recent XenForo build and access to server configuration

• Willingness to treat structured data as a first-class feature (not “SEO garnish”)

• Basic familiarity with JSON-LD, schema.org, and canonicalization strategies

• The ability to test with crawler/validator tools and analyze server logs



🗺️ Overview

Step #1
> Understand why LLMO JSON-LD must be core, not an add-on

Step #2 > Map forum entities to schema.org (threads, posts, users, categories, resources)

Step #3 > Design a stable, permission-aware JSON-LD emitter in XenForo core

Step #4 > Validate, monitor, and version the schema across releases



📊 Difficulty level

Step #1
> Easy > framing and goals that align the team

Step #2 > Medium > entity mapping and edge-case handling

Step #3 > Medium > core engineering (routing, permissions, performance)

Step #4 > Easy > repeatable validation and governance



🎯 Objectives

Establish JSON-LD as a built-in capability across all XenForo content types

Guarantee permission-aware, canonical, deduplicated entities for LLMs

Prevent schema drift and add-on fragmentation through versioned governance

Improve AI-era discoverability, attribution, and click-through from AI overviews



⚠️ Common pitfalls

• Treating LLMO as “just SEO” and shipping it as an optional plugin—results are brittle and inconsistent

• Emitting JSON-LD without permission checks—LLMs may index content users cannot see

• Duplicating entities (thread vs. paginated view vs. AMP) and losing canonical authority

• Schema drift across add-ons—LLMs ingest conflicting graphs, harming trust and ranking

• Neglecting performance—heavy, per-request JSON assembly can hurt TTFB without caching



🛠️ Step-by-step

🔹 Step #1 > Make the core case: why add-ons aren’t enough

1️⃣ Main action

• Anchor LLMO in XenForo core roadmap: structured data should be generated by the same layer that knows routes, permissions, languages, and canonical links.

2️⃣ Why it matters

• LLMs reward stable, permission-correct graphs. Only the core can guarantee consistency across threads, posts, resources, tags, and paginated views.

3️⃣ Explanation

• Add-ons fragment schemas, lag core releases, and can’t reliably mirror privacy rules. Core integration ensures one authoritative JSON-LD per entity, every time.



🔹 Step #2 > Map forum entities to schema.org

1️⃣ Main action

• Define a canonical mapping for forum primitives: Thread → DiscussionForumPosting/QAPage context; Post → Comment; Forum/Category → CollectionPage; Resource/Addon → SoftwareSourceCode/SoftwareApplication when relevant; User (public profile) → Person/Organization as allowed.

2️⃣ Why it matters

• Clear, unambiguous types reduce hallucinations and improve attribution in AI answers.

3️⃣ Explanation

• Include author, datePublished/Modified, acceptedAnswer (for solved threads), inLanguage, keywords/tags, interaction statistics, and sameAs links where appropriate.



🔹 Step #3 > Engineer a permission-aware JSON-LD emitter in core

1️⃣ Main action

• Build a server-side emitter invoked by controllers that resolves the entity graph before rendering, honoring permissions, canonical URLs, and noindex rules.

2️⃣ Why it matters

• LLMs must never ingest data a guest cannot view. Core routing/permission logic is the single source of truth.

3️⃣ Explanation

• Cache per-entity JSON-LD fragments, purge on edits/moderation, and attach them exactly once per canonical page. Expose a registry so add-ons can extend (not replace) the graph.



🔹 Step #4 > Validate, monitor, and version the schema

1️⃣ Main action

• Add automated tests and a public schema changelog versioned with XenForo releases.

2️⃣ Why it matters

• Stable, predictable graphs keep LLMs confident in your forum as a source; regressions become visible fast.

3️⃣ Explanation

• Ship an admin tool to preview the JSON-LD for any URL as a guest/user/mod. Log crawler requests and compare emitted graph size, fields, and entity count over time.



🧮 What JSON-LD changes in practice for LLMs

Context binding → Threads become explicit entities with topic, author, dates, tags, language.

Answer extraction → Accepted answers and high-signal posts are findable without scraping.

Attribution path → Canonical URLs and authorship travel with the content into AI overviews.

De-duplication → Canonicals and identifiers prevent “same thread, many URLs” confusion.

Safety → Permission-aware emission keeps private or flagged content out of LLM corpora.



🪟 Differences across deployments

• Self-hosted forums need server-side caching and log-based validation to keep TTFB low.

• Heavily customized sites must extend, not fork, the core JSON-LD registry to avoid drift.

• Multi-language communities should emit inLanguage and alternate relationships per locale.



💡 Pro tip

Treat JSON-LD as “documentation for machines.” Keep it as carefully curated as human docs: version it, review it, and test it on every release like any breaking API.



🚫 Do not

Do not rely on a theme snippet or add-on alone for LLMO. Without core integration, you will leak private data, duplicate entities, and send conflicting signals to LLMs.



🧑‍💻 Professional advice

Create a small governance group (product, SEO, engineering, moderation). Own the schema map, run pre-release validations, and publish a public “Structured Data Contract” so plugin authors can extend your graphs safely.



🔗 Further reading

Schema.org (types and properties)

JSON-LD (syntax and best practices)

Structured data fundamentals (Google)

Content & safety concepts for AI ingestion

Webmaster guidelines (Bing)



✅ Key takeaway

LLMO with JSON-LD is core infrastructure, not a plug-in: integrate it into XenForo’s source so every thread, post, and resource emits a consistent, permission-aware, canonical graph that AI systems can trust.
 
Back
Top Bottom