Skip to content

fix(landing): valid docs breadcrumb JSON-LD, plus guides, storage and quickstarts hub pages - #460

Merged
AminDhouib merged 3 commits into
devfrom
fix/docs-breadcrumb-hubs
Sep 27, 2026
Merged

AminDhouib merged 3 commits into
devfrom
fix/docs-breadcrumb-hubs

Conversation

@AminDhouib

Copy link
Copy Markdown
Member

Why

Since #451 went to production (2026-09-27 03:16Z), every docs page inside a folder emits a BreadcrumbList whose middle crumbs have no URL. For example, /docs/guides/storage/azure-blob/ emits:

1 Docs      https://useupup.com/docs/
2 Guides    (no item)
3 Storage   (no item)
4 Upload Files to Azure Blob Storage from the Browser   …/azure-blob/

schema.org accepts a crumb with only a name, but Google does not. Google's breadcrumb rules require item on every ListItem; the only exception is "If the breadcrumb is the last item in the breadcrumb trail, item is not required." So these pages can't get breadcrumb rich results, and they will show up as invalid in Search Console's Breadcrumbs report.

The folder URLs (/docs/guides/, /docs/guides/storage/, /docs/quickstarts/, /docs/api-reference/, /docs/guides/auth/, /docs/guides/server-adapters/) all return 404 on production today.

What changed

Three commits, all under apps/landing/**, so only the docs E2E route runs.

  1. fix(landing): breadcrumb JSON-LD
    • The JSON-LD trail drops crumbs that have no URL and numbers the rest 1..n. The page's own crumb is always last and keeps its URL.
    • The visual breadcrumb is unchanged.
    • A new test walks every docs page, not a sample, and checks that:
      • every crumb except the last has an item;
      • positions run 1..n;
      • every item is an existing docs page;
      • the last crumb is the page itself.
    • Against the old code it failed with 85 violations (expected [ …(85) ] to deeply equal [], all "has no item").
  2. feat(landing): docs hub pages
    • Adds real pages for /docs/guides/, /docs/guides/storage/ and /docs/quickstarts/, taken from feat(landing): brand entity schema, docs FAQPage, docs hub pages #445 and re-checked against current dev:
      • no links to nested hub URLs, because dev's link check still resolves guides/storage/index.mdx wrongly (the fix is in feat(landing): brand entity schema, docs FAQPage, docs hub pages #445);
      • the image editor is described as React/Preact-only;
      • Azure is described as client mode only;
      • server mode is described as signing uploads, with the browser sending files straight to the bucket;
      • framework versions match the current peer ranges.
    • Those folders now appear in the breadcrumb trail with a URL, and the sidebar labels become links.
    • The docs page count goes from 69 to 72, and the sitemap, llms and source pins were updated to match.
  3. fix(landing): comparisons folder index
    • comparisons/meta.json listed index as an ordinary page, so fumadocs didn't treat it as the folder index.
    • With that entry removed, the sidebar's "Comparisons" label links to the hub, and the comparison and roundup pages emit Docs → Comparisons → page.
    • /docs/comparisons/ content, title, canonical URL, sidebar order and prev/next are unchanged.

Folders that still have no hub page (api-reference, auth, server-adapters, processing) simply drop out of the JSON-LD trail.

Not in this PR: the legacy redirect retarget (/documentation/quickstarts → the new hub), the check-links nested-index fix, and the docs.spec.ts edits. They stay in #445 because they touch scripts/ and apps/e2e-test/, which route the GHCR-blocked MinIO suite.

Checks

Run on the build host, with raw exit codes:

  • landing vitest: 254/254;
  • landing typecheck, lint and build;
  • prettier --check on every touched file;
  • vocab:check and test:quality (run after git add);
  • docs:links:check (529 links, 72 pages), docs:api-sync:check, docs:snippets:coverage and docs:snippets:check.

All exited 0.

A production build was served with next start:

  • /docs/guides/, /docs/guides/storage/, /docs/quickstarts/ and /docs/comparisons/ return 200.
  • The sidebar renders Guides, Storage, Quickstarts and Comparisons as links.
  • BreadcrumbList on the checked pages:
    • Azure guide: Docs → Guides → Storage → page, all with item.
    • api-reference/upupuploader/ref-api: Docs → page.
    • React quickstart: Docs → Quickstarts → page.
    • Angular roundup: Docs → Comparisons → page.

I read all of apps/e2e-test/landing/docs.spec.ts. Its only comparisons assertion is the legacy redirect to upup-vs-uppy, which this PR doesn't change. Its only prev/next pin is theming ↔ accessibility, which is also unaffected.

Every docs page nested in a sidebar folder that has no URL of its own
emitted a BreadcrumbList whose middle ListItems carried only a name.
(comparisons/ has a hub page, but its meta.json lists `index` as a child
page, so fumadocs leaves the folder node itself without a URL.) schema.org
allows that, but Google's breadcrumb rich result requires `item` on
every ListItem except the last
(https://developers.google.com/search/docs/appearance/structured-data/breadcrumb),
so those trails were invalid for the rich result.

DocsStructuredData now drops name-only folder crumbs from the JSON-LD
trail and numbers positions 1..n over what remains. The page's own crumb
is always last and keeps its URL. The visual <DocsBreadcrumb> is
unchanged.

The new test walks every docs page, not a sample. In each BreadcrumbList,
every ListItem except the last must have an `item`, positions must run
1..n, every `item` must be an existing docs page or /docs/, and the last
crumb must be the page itself.

RED against the unfixed component (vitest run
src/__tests__/docs-structured-data.test.ts, exit 1; 85 entries, elided):

 FAIL  src/__tests__/docs-structured-data.test.ts > docs BreadcrumbList JSON-LD meets the Google breadcrumb rules > numbers every trail 1..n and links every crumb but the last to a docs page
AssertionError: expected [ …(85) ] to deeply equal []

- Expected
+ Received

- []
+ [
+   "/docs/api-reference/azure-generate-sas-url: \"API reference\" has no item",
+   "/docs/api-reference/error-codes: \"API reference\" has no item",
+   "/docs/api-reference/events: \"API reference\" has no item",
    ...
+   "/docs/comparisons/upup-vs-uppy: \"Comparisons\" has no item",
    ...
+   "/docs/guides/storage/azure-blob: \"Guides\" has no item",
+   "/docs/guides/storage/azure-blob: \"Storage\" has no item",
    ...
+   "/docs/migration/v1-to-v3: \"Migration\" has no item",
+   "/docs/quickstarts/angular: \"Quickstarts\" has no item",
    ...
+   "/docs/quickstarts/vue: \"Quickstarts\" has no item",
+ ]

 Test Files  1 failed (1)
      Tests  1 failed | 27 passed (28)

GREEN after the fix: the same file passes 28/28 (exit 0), and the full
landing suite passes 246/246 across 20 files (exit 0).
Add index pages for guides/, guides/storage/ and quickstarts/, carved from
#445. Each folder now has a real URL, so its sidebar label is a link and
its breadcrumb crumb is a link. The Azure trail is now Docs (/docs/) →
Guides (/docs/guides/) → Storage (/docs/guides/storage/) → page. The
Guides, Storage and Quickstarts crumbs that the previous commit dropped
from the JSON-LD for having no URL are back, now with one.

The hubs list their child pages. I re-checked every claim against the
current dev docs:
- The storage hub says what the modes and storage-providers guides say:
  in both modes the browser uploads bytes directly to the bucket over
  short-lived signed URLs, and server mode signs those URLs and writes
  cloud-drive files to storage itself.
- Azure Blob is client mode only: createUpupHandler rejects it.
- The provider list matches guides/storage/, the framework list matches
  quickstarts/, and the peer ranges match packages/*/package.json:
  React 19, Vue 3.4+, Svelte 5, Angular 19 (^19.0.0), Next.js 15+.

Changes from #445's versions:
- No MDX link points at a nested hub URL. dev's
  scripts/docs/check-links.mjs still resolves guides/storage/index.mdx to
  /docs/guides/storage/index, and a probe link to /docs/guides/storage/
  failed it with "page /docs/guides/storage does not exist". So the
  guides hub links each storage page directly, and its "New to upup?"
  line points at Getting Started instead of /docs/quickstarts/.
- The quickstarts description no longer says "the same features
  everywhere". The image editor ships for React and Preact only, and the
  hub now says so, in the FAQ's words.
- The storage hub no longer says every guide covers a CORS rule.
  s3-compatible.mdx has no CORS step.

guides/meta.json gains "title": "Guides" so the folder keeps its sidebar
name now that it has an index page. The DocsPageNav comment's example of
a URL-less folder moves from Quickstarts to Auth.

Pins: the docs page count goes from 69 to 72 in docs-source, docs-llms
and the seo-surfaces sitemap total. docs-structured-data now covers all
four hubs:
- the sitemap, llms.txt and markdown-twin checks run for every hub;
- the served-twin check uses guides/storage, the two-level case;
- "links every child" leaves out only the nested hub page itself;
- a new test pins the full Azure BreadcrumbList.
comparisons/meta.json listed "index" in `pages`. fumadocs then treats
comparisons/index.mdx as an ordinary child page and gives the folder
itself no index. So the sidebar's "Comparisons" label was plain text,
with the hub repeated below it as a child entry, and every comparison
page's breadcrumb had a Comparisons crumb with no URL. The breadcrumb
JSON-LD fix earlier on this branch drops such crumbs, so those pages
emitted only Docs → page.

With "index" removed, the hub is the folder index:
- The sidebar "Comparisons" label links to /docs/comparisons/. The seven
  comparison pages keep their order below it.
- Comparison pages emit Docs → Comparisons (/docs/comparisons/) → page.
- /docs/comparisons/ still returns 200 with an unchanged MDX source,
  title, H1, canonical URL and markdown twin.
- Prev/next is unchanged: faq → comparisons → best-react → best-vue ...

The hub-trail test becomes a table and pins the full trail for
best-angular-file-upload-libraries next to the Azure one. Against the
old meta.json it fails (exit 1):

  - "item": "https://useupup.com/docs/comparisons/",
  - "name": "Comparisons",
  - "position": 2,
  ...
  - "position": 3,
  + "position": 2,

apps/e2e-test/landing/docs.spec.ts has no sidebar, prev/next or overview
assertion for comparisons. It has only the /documentation/comparisons
redirect, which next.config.mjs owns.
@codesandbox

codesandbox Bot commented Sep 27, 2026

Copy link
Copy Markdown

Review or Edit in CodeSandbox

Open the branch in Web Editor • VS Code • Insiders

Open Preview

@AminDhouib
AminDhouib merged commit f0d7600 into dev Sep 27, 2026
18 checks passed
@AminDhouib
AminDhouib deleted the fix/docs-breadcrumb-hubs branch September 27, 2026 04:26
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.

1 participant