fix(landing): valid docs breadcrumb JSON-LD, plus guides, storage and quickstarts hub pages - #460
Merged
Merged
Conversation
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.
Review or Edit in CodeSandboxOpen the branch in Web Editor • VS Code • Insiders |
This was referenced Sep 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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:schema.org accepts a crumb with only a name, but Google does not. Google's breadcrumb rules require
itemon everyListItem; the only exception is "If the breadcrumb is the last item in the breadcrumb trail,itemis 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.fix(landing): breadcrumb JSON-LDitem;itemis an existing docs page;expected [ …(85) ] to deeply equal [], all "has no item").feat(landing): docs hub pages/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:guides/storage/index.mdxwrongly (the fix is in feat(landing): brand entity schema, docs FAQPage, docs hub pages #445);fix(landing): comparisons folder indexcomparisons/meta.jsonlistedindexas an ordinary page, so fumadocs didn't treat it as the folder index./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 thedocs.spec.tsedits. They stay in #445 because they touchscripts/andapps/e2e-test/, which route the GHCR-blocked MinIO suite.Checks
Run on the build host, with raw exit codes:
prettier --checkon every touched file;vocab:checkandtest:quality(run aftergit add);docs:links:check(529 links, 72 pages),docs:api-sync:check,docs:snippets:coverageanddocs: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.item.api-reference/upupuploader/ref-api: Docs → page.I read all of
apps/e2e-test/landing/docs.spec.ts. Its only comparisons assertion is the legacy redirect toupup-vs-uppy, which this PR doesn't change. Its only prev/next pin is theming ↔ accessibility, which is also unaffected.