Skip to content

fix(seo): resolve Ahrefs audit findings — orphan pages, double-slash URLs, indexing, alt text#531

Merged
vvlladd28 merged 6 commits into
thingsboard:mainfrom
rusikv:fix/ahrefs-post-release-report
Jul 1, 2026
Merged

fix(seo): resolve Ahrefs audit findings — orphan pages, double-slash URLs, indexing, alt text#531
vvlladd28 merged 6 commits into
thingsboard:mainfrom
rusikv:fix/ahrefs-post-release-report

Conversation

@rusikv

@rusikv rusikv commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Resolves the in-repo findings from the post-release Ahrefs SEO audit of the docs site.

Changes

1. Orphan Edge pages → redirects
Removed stale Edge user-guide pages that nothing linked to (several had been renamed) and added redirects from the old URLs to their live replacements. The one reference page with no direct successor redirects to the PE reference landing page.

2. Double-slash URL guard (/iot-hub//<slug>/)
IoT Hub links are composed from /iot-hub/ + category slug + item slug. When the backend returns an item whose type has no docs-site category (e.g. the DASHBOARD type, live server-side but hidden on the site), the category slot was empty and produced a // URL. Both link builders (server-rendered cards and live search results) now skip such items instead of emitting a malformed href — matching the existing getListingHref → '#' / groupResults → continue patterns.

3. Make /iot-hub/search/ indexable
Removed the noIndex flag from the IoT Hub search page so search engines can list it.

4. Alt text on IoT Hub category tiles
Category tile images now use the category name as alt text (was empty).

5. Remove retired /community/ page
Deleted the page, redirected /community/ → homepage, and removed it from the OG-image allowlist / marketing-meta so the build no longer generates a card for a nonexistent page.

Verification

  • astro check — 0 errors, 0 warnings, 0 hints
  • eslint — clean
  • lint:linkcheck — full build (4466 pages), no link issues

Out of scope (IoT Hub CMS team)

Three audit findings live in the IoT Hub CMS, not this repo, and are not addressed here: device-library resource JSON redirect, an oversized image, and CMS-authored image alt text.

rusikv added 4 commits July 1, 2026 14:58
Delete 5 orphan Edge pages (no incoming links; live replacements exist):
provision-customer, provision-user (→ provision-customers-and-users), provision-dashboard
(→ dashboards), the edge-proxy parent Tabs page (→ edge-proxy/debian; children kept), and the
empty pe/reference/configuration-reference stub (→ /docs/pe/reference/). Removes CE + PE stubs +
the now-unused shared includes, adds the /user-guide/ + pe/ redirects.

Remove the unlinked /community/ marketing page: delete the page, drop it from
MARKETING_ALLOWLIST + the OG marketing-meta prefixes, and redirect /community/ → /.

public/_redirects + public/redirects.json regenerated via pnpm generate:redirects.
An item whose itemType has no public category (e.g. DASHBOARD, hidden for the first
release) produced /iot-hub//<slug>/. Guard the card-bind href to '#' and skip unknown
types in the single-category grid, matching the existing groupResults/getListingHref pattern.
Remove the noIndex prop — the page is a full paginated IoT Hub catalog listing, a legit
index page, not a query-driven search-results page.
CategoryTile emitted alt="" for the meaningful category image; use the category label.

@vvlladd28 vvlladd28 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Reviewed 23 changed files in fix(seo): resolve Ahrefs audit findings — orphan pages, double-slash URLs, indexing, alt text. Left 4 comments inline.

The cleanup is well-targeted — I verified every redirect target resolves to a real page (no redirect→404 chains), there are no dangling internal links to the deleted pages, and the double-slash guard reasoning is sound. One correctness issue stands out: the PE-variant redirects point at the wrong source URLs, so the canonical PE pages you deleted now 404 (inline on redirects.ts). I also re-checked the changes against the SEO audit — most in-scope items are done, but the alt-text cleanup is only partially addressed; see Additional findings.

Additional findings

These are about existing code outside the PR's diff — spotted while cross-checking the audit's missing-alt-text findings (ignoring the oversized-image item as requested).

  • src/components/IotHub/CreatorHero.astro:33 — the creator avatar's alt should be the creator name; it's still alt="". The <img> sits inside an aria-hidden="true" wrapper, so screen-reader behavior is already correct (the name is in the adjacent <h1>) — but the Ahrefs "missing alt text" crawler flags empty alt regardless, so creator pages likely still appear in the audit. Not touched by this PR.
  • src/components/IotHub/CategoryHero.astro:75 — the category hero-cluster image is still alt=""; the audit flagged it as "meaningful". It's arguably decorative (illustration beside a heading), but it's unaddressed.
  • src/components/IotHub/IotHubNoResults.astro:29 and IotHubFetchError.astro:26 — still alt="". These are decorative, so leaving them empty is defensible — just noting the audit may still count them.
  • src/pages/iot-hub/search/[...page].astro:81 — a more catalog-style title than plain "Search" was suggested; it's still Search | IoT Hub | ThingsBoard. Optional, and the noIndex removal (the core of that item) is done.

Net: the orphan-page cleanup, double-slash guard, search indexing, and /community/ removal are all addressed in this repo (the orphan cleanup with the redirect caveat above); the alt-text pass only covers the category tiles (via CategoryTile.astro), leaving the creator avatar and hero-cluster images untouched. The device-library JSON redirect, oversized image, and CMS-authored alt text are correctly out of scope (iot-hub CMS).


This review was auto-generated. Findings may contain errors — please verify before applying changes.

Comment thread src/data/redirects.ts
{ oldPath: 'pe/edge/config/subscribe-to-attribute', target: '/docs/edge/pe/user-guide/attribute-sync/' },
// Orphan Edge user-guide pages removed — redirect to their live replacements.
{ oldPath: 'edge/user-guide/provision-customer', target: '/docs/edge/user-guide/provision-customers-and-users/' },
{ oldPath: 'pe/edge/user-guide/provision-customer', target: '/docs/edge/pe/user-guide/provision-customers-and-users/' },

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These four pe/edge/user-guide/... entries redirect the legacy URL scheme (/docs/pe/edge/...), but the pages you deleted were live at the canonical PE URLs — /docs/edge/pe/user-guide/{provision-customer,provision-user,provision-dashboard,edge-proxy}/ (that's where the deleted edge/pe/... stubs rendered, and where the pe/edge → edge/pe catch-all pointed search engines). After this PR nothing redirects those canonical URLs, so they now 404 — which reintroduces the exact orphan/404 problem this PR fixes, for the PE half of each deleted page.

The CE variants above are fine because a CE page's canonical URL is the edge/user-guide/... oldPath. For PE you'd want the canonical source too, e.g.:

{ oldPath: 'edge/pe/user-guide/provision-customer', target: '/docs/edge/pe/user-guide/provision-customers-and-users/' },
{ oldPath: 'edge/pe/user-guide/provision-user',     target: '/docs/edge/pe/user-guide/provision-customers-and-users/' },
{ oldPath: 'edge/pe/user-guide/provision-dashboard', target: '/docs/edge/pe/user-guide/dashboards/' },
{ oldPath: 'edge/pe/user-guide/edge-proxy',          target: '/docs/edge/pe/user-guide/edge-proxy/debian/' },

Keep the existing pe/edge/... entries as well so the legacy URL doesn't chain into a 404. (I confirmed all the targets resolve to real pages — this is purely about the missing canonical sources.)

const categorySlug = cat?.slug ?? '';
// Unknown item type has no public category — skip rather than emit
// cards with a `/iot-hub//slug/` href (matches groupResults).
if (!cat) return;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same "itemType has no public category" condition is now handled three ways across these files: getListingHref returns '#', bindListingCard returns '#', and here renderResults early-returns and drops the whole grid. For this single-category branch the early return is actually the right call — every item shares the page's itemType, so if that type has no category there's genuinely nothing renderable. Just flagging that one condition with three different fallbacks is easy to trip over later; a shared helper for the cat-lookup + skip/'#' decision would make it one choice instead of three. Not blocking.

// Root href. An empty categorySlug (item type with no public category, e.g.
// a type the site doesn't surface) would yield `/iot-hub//slug/` — guard it
// to '#' instead, matching getListingHref.
root.setAttribute('href', categorySlug ? `/iot-hub/${categorySlug}/${item.slug}/` : '#');

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heads-up that bindListingCard has no runtime caller — the only reference is a commented-out usage example in ListingCardTemplate.astro. So this new empty-slug guard hardens a path nothing currently exercises, and the comment asserting parity with getListingHref isn't exercised either. Fine to keep for symmetry, but worth confirming whether the binder is still meant to be wired up (or removed) rather than left as dead code.

</span>
<div class="iot-hub-tile__img">
<Image src={image} alt="" widths={[400, 600, 800]} sizes="(max-width: 750px) 100vw, 400px" />
<Image src={image} alt={label} widths={[400, 600, 800]} sizes="(max-width: 750px) 100vw, 400px" />

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor a11y note (not blocking): the tile already renders {label} as visible text inside the same <a>, so alt={label} makes a screen reader announce the category name twice per link — its sibling CategoryHero.astro keeps alt="" for exactly this decorative-image-next-to-visible-text case. That said, since the whole point here is passing the Ahrefs "missing alt" audit (which flags empty alt), alt={label} is a reasonable tradeoff and I wouldn't revert it to "" without confirming the audit tolerates empty alt. Just noting the redundant SR output.

rusikv added 2 commits July 1, 2026 17:19
Orphan-page redirects were added only for the legacy pe/edge/ URL scheme, so the canonical edge/pe/ URLs of the deleted PE stubs 404'd. Add canonical sources (keep legacy aliases for one-hop resolution).

Add alt text to the category hero and creator avatar images (creator avatar now exposed to the a11y tree as 'Photo of <name>'), and make the category-tile alt descriptive to avoid a duplicate screen-reader announcement.
…L byte

- Add getCategoryForItemType() as the single itemType->category lookup (Map-backed, O(1)); route getListingHref and dynamic-search grouping/rendering through it and drop the duplicate module-local map.
- Set the IoT Hub search page title to 'IoT Hub Catalog | ThingsBoard' so the now-indexable page reads as a catalog.
- Remove a stray NUL byte in iot-hub-dynamic-search.ts (a typo for a space in an internal dedup key) that made the file read as binary and hid it from grep-based tooling.
- Document bindListingCard's real runtime caller.
@vvlladd28 vvlladd28 merged commit e829c49 into thingsboard:main Jul 1, 2026
4 of 5 checks passed
vvlladd28 added a commit that referenced this pull request Jul 1, 2026
…s/; SEO alt text + search noindex (#533)

* fix(iot-hub): relocate device-library resources under /iot-hub/devices/

Move public/device-library/resources/ -> public/iot-hub/devices/resources/
so the existing device-library -> IoT Hub splat redirects resolve to real
files. Both /docs/devices-library/resources/* and /device-library/resources/*
already 301 into /iot-hub/devices/:splat; the assets now live at that target,
fixing broken links (e.g. the lrs10701 air-quality dashboard JSON referenced
from external sites we cannot edit). No project code references these paths.

* fix(seo): alt text on remaining IoT Hub images; keep search page noindexed

Follow-up to the Ahrefs audit (#531):
- Give the last four empty-alt IoT Hub images meaningful alt text: the
  no-results and fetch-error illustrations (aria-hidden kept so screen
  readers aren't given a redundant announcement), the detail creator
  avatar, and the listing-link thumbnail.
- Revert /iot-hub/search/ to its original title and restore noIndex.
  Removing it wholesale also un-noindexed the paginated variants
  (/iot-hub/search/2/ ...), which self-canonicalize and would be indexed
  as near-duplicates; keep the page out of the index for now.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants