Repository navigation
docs(seo): version-explicit metadata, canonical consolidation, and descriptive link text - #8239
Conversation
…e fragment lint Versioned docs pages (orm/v6, orm/v7, cli/v7, guides/v7, /v7) shared their title, meta description, and H1 with their twin in every other version tree. generateMetadata now folds the version into the title and description, the H1 area carries a version badge, and the markdown rendition's title line matches. Adds an optional `canonical` frontmatter field and points the twelve ORM v6 pages whose body is identical to their v7 twin at that twin. lint:links now checks same-page `#fragment` links, which next-validate-link skips; fixes the one stale bookmark it found (audit 3.4).
…tale docs notes
Rewrites 157 non-descriptive anchors ("here", "learn more", "docs", ...) in
apps/docs content so the link text names its destination, via a re-runnable
script that takes the name from the target page's frontmatter title or heading.
Differentiates four same-tree metadata duplicates (embed-studio, the Prisma
Postgres query-insights overview, and the v6/v7 error troubleshooting indexes)
and adds a test that fails when two pages in one version tree share
metaTitle + metaDescription.
Structured data now carries the same version label and canonical as the page
metadata; CLAUDE.md describes how versions actually work; the unused
version-banner component is gone.
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
🍈 Lychee Link Check Report2454 links: ✅ All links are working!Full Statistics Table
|
…dropped noun - structured-data: mainEntityOfPage.@id prefixed the base URL onto every resolved canonical, so an absolute canonical: frontmatter value (which resolveCanonicalUrl passes through by design) would render as https://www.prisma.iohttps://... . Join only relative canonicals; covered by two tests on the new absoluteCanonicalUrl helper. - .claude/CLAUDE.md: the versioned getting-started tree lives under content/docs/(index)/v7/, not content/docs/v7/. - shared-extensions (v6, v7): the anchor rewrite absorbed the word "page" and left "visit the Shared packages & examples."; drop the article.
|
Reviewed Three findings, fixed and pushed in
I will re-review the new head from scratch shortly. — reviewer |
mentionsVersion only recognised the full label ('Prisma ORM v7') or the
bare 'v7' token, so titles that name the version without the v still got
the label appended: 'What is Prisma 7?' rendered as 'What is Prisma 7?
(Prisma ORM v7)' and 'What is Prisma ORM? (Prisma 6 overview)' as '...
(Prisma 6 overview) (Prisma ORM v6)', the stutter the helper's own doc
says it avoids. It now also matches 'Prisma [ORM|CLI] N' (N followed by a
non-digit, so 'Prisma 6.16' counts and 'Prisma 60' does not). Tests
added for both directions.
|
Re-reviewed One finding, fixed and pushed in
Everything else read clean. I will re-review the new head from scratch shortly. — reviewer |
|
Review clean at d613787 Third cold read (fresh clone, branch 0 behind What the PR does (all in Risk: low. Every change is confined to the docs app and its content; the helpers are unit-tested including the edge cases found in rounds 1–2; the anchor rewrites change only link text and every href still resolves; the canonicals are reversible frontmatter on pages whose bodies are provably identical. The one thing not verified in a browser (the Vercel preview is login-gated) is the badge placement next to the H1 — it is a flex-wrap wrapper around Proposed follow-up (awaiting a human's approval, not blocking this PR): a builder task to drop pages with a foreign canonical from — reviewer |
|
Review clean at 782cf66 Cold read of the new head after the merge of What the PR does (all in Risk: low. Everything is confined to the docs app and its content, the helpers are unit-tested including the edge cases found in earlier rounds, every rewritten anchor still resolves, and the canonicals are reversible frontmatter on provably identical pages. Not verified in a browser (the Vercel preview is login-gated): the badge placement beside the H1, which is cosmetic at worst. One cosmetic note, not a fix: two v7 comparison pages now link "[Type safety Overview]" because that is the v7 page's own frontmatter The sitemap follow-up (drop or deprioritise pages with a foreign canonical in — reviewer |
SEO audit remediation, work package 4 of 8 (audit 2.3 duplicate titles/descriptions/H1s, 3.3 generic anchor text, 3.4 stale bookmark), all in
apps/docs.Versioned docs pages (
orm/v6,orm/v7,cli/v7,guides/v7,/v7) shared their<title>, meta description, and H1 with their twin in every other version tree, including the unversioned Prisma 8 tree. Both v6 and v7 stay indexable; the version is now explicit in the metadata, and a canonical is used only where two versioned URLs are the same document.What it delivers
A. Version-explicit titles, descriptions, and H1 marker
src/lib/version-metadata.ts(new):getPageVersion(pageUrl)→{ version, label }ornullfor latest/unversioned, built on the existing pathname helpers inversion.ts. Labels:/orm/v6/*→ "Prisma ORM v6",/orm/v7/*→ "Prisma ORM v7",/cli/v7/*→ "Prisma CLI v7",/guides/v7/*and/v7/*→ "Prisma 7". PluswithVersionTitle/withVersionDescription, which skip pages whose text already names the version. NamedgetPageVersion/getPageVersionLabelrather thangetVersionLabelbecauseversion.tsalready exports agetVersionLabel(version).generateMetadatafolds the label in: title →Supported databases (Prisma ORM v6), description →… Applies to Prisma ORM v6.. OG and Twitter read the same variables, so they stay in sync. The%s | Prisma Documentationtemplate is untouched.Badgewith the label next to (never inside)<DocsTitle>. Note:badge:frontmatter is only rendered in the sidebar today (sidebar-badge-provider.tsx), not on the page, so there was no existing page badge to sit beside — the version badge reuses that component and itsrounded-full!pattern..md/llms.mdxrendition's title line carries the same label (get-llm-text.ts), so agents see the version too.structured-data.tsx: theTechArticleheadline/description use the same labelled values, andmainEntityOfPage.@idnow points at the canonical instead of always at the page's own URL.B. Canonical consolidation
New optional
canonicalfrontmatter field (source.config.ts), resolved bysrc/lib/canonical.ts(page.data.canonical ?? self,/docsbase path applied, absolute URLs passed through).scripts/find-version-twin-duplicates.ts(new, re-runnable,--checkmode) compares every v6 page against its v7 twin with frontmatter stripped and/orm/v6/normalised to/orm/v7/. 12 of 133 pairs are materially identical (5 byte-identical, 7 differing only by version paths); each v6 page now canonicals to its v7 twin. Nonoindexwas added.orm/v6/more/troubleshooting/bundler-issues/orm/v7/more/troubleshooting/bundler-issuesorm/v6/more/troubleshooting/check-constraints/orm/v7/more/troubleshooting/check-constraintsorm/v6/more/troubleshooting/graphql-autocompletion/orm/v7/more/troubleshooting/graphql-autocompletionorm/v6/more/troubleshooting/nuxt/orm/v7/more/troubleshooting/nuxtorm/v6/more/troubleshooting/raw-sql-comparisons/orm/v7/more/troubleshooting/raw-sql-comparisonsorm/v6/more/comparisons/prisma-and-mongoose/orm/v7/more/comparisons/prisma-and-mongooseorm/v6/more/dev-environment/environment-variables/orm/v7/more/dev-environment/environment-variablesorm/v6/prisma-client/client-extensions/type-utilities/orm/v7/prisma-client/client-extensions/type-utilitiesorm/v6/prisma-client/debugging-and-troubleshooting/debugging/orm/v7/prisma-client/debugging-and-troubleshooting/debuggingorm/v6/prisma-client/setup-and-configuration/database-polyfills/orm/v7/prisma-client/setup-and-configuration/database-polyfillsorm/v6/prisma-schema/data-model/multi-schema/orm/v7/prisma-schema/data-model/multi-schemaorm/v6/reference/supported-databases/orm/v7/reference/supported-databasesThe two audit "Duplicate content" hits,
/orm/v7/more/troubleshooting/graphql-autocompletionand/orm/v7/prisma-schema/data-model/multi-schema, duplicate their v6 twins (not an unversioned page: no v6 or v7 page is identical to its Prisma 8 counterpart — verified by the same comparison run against the unversioned tree). Both are covered by the v6 → v7 canonicals above, so the v7 URL is the one that consolidates.C. Cross-section duplicates — metadata rewritten from each page's own H1 and intro:
guides/integrations/embed-studioPrisma MCP Server/ descManage Prisma Postgres databases using LLMs with the Prisma Model-Context-Protocol (MCP) ServerEmbed Prisma Studio in a Next.js app/Embed Prisma Studio in a Next.js application with @prisma/studio-core so you can browse and edit your database from inside your app.postgres/database/query-insightsQuery Insights with Prisma Postgres/Inspect slow queries, connect Prisma calls to SQL, and apply focused fixes with Prisma Postgres.Query Insights for a Prisma Postgres database/How Query Insights surfaces slow queries for a Prisma Postgres database in the Prisma Console, and where the full documentation lives.orm/v6/reference/errors/indexErrors/Prisma Client, Migrate, Introspection error message referencePrisma ORM error troubleshooting index/Where to find help for common Prisma ORM errors: connection pool, migration, shadow database, baselining, and MongoDB replica set issues.orm/v7/reference/errors/indexDecisions:
/query-insights(the dedicated section, 183 lines) stays primary and is unchanged; the Prisma Postgres page is a short overview that already links to it, so it was differentiated rather than canonicalised. The fourai/tools/*pages and theirorm/v6/more/ai-tools/*copies differ in body (checked), so they got no canonical — Part A's "(Prisma ORM v6)" label differentiates the v6 copies. Theerror-referencepages keep their metadata; only theerrors/hub pages (the non-primary of each same-tree pair) were rewritten.D. Stale bookmark + a gap in the link linter
orm/v7/.../opentelemetry-tracing.mdxlinked#step-1-install-up-to-date-prisma-orm-dependencies; the heading is now "Step 1. Install Prisma ORM dependencies". Fixed (the v6 copy still has the old heading and was correct already).pnpm lint:linksdid not catch it:next-validate-linkresolves fragments only on links that carry a path, so same-page bookmarks pass silently.scripts/lint-links.tsnow checks#fragment-only links andhref="#..."against the page's toc ids, headings (including ones nested in MDX components), andid="…"attributes. Across all 688 pages it reported exactly that one link and no false positives. Fence tracking moved toscripts/lib/markdown-regions.tsand now honours CommonMark (a closing fence carries no info string) — the old "count every fence line" approach desynchronised on nested examples and hid the tail of long files such asprisma-client-reference.mdx.E. Generic anchor text —
scripts/rewrite-generic-link-text.ts(new, re-runnable,--checkmode) rewrote every anchor whose text was exactly one of the 14 generic phrases: 157 links on 154 lines in 65 files. It names the destination from the target page's frontmattertitle, or the target heading when the link has a fragment; external destinations come from a curated name table and are never guessed (a destination it cannot name is reported and left alone — none remain). Hrefs are unchanged; code blocks and headings are skipped. Output was reviewed line by line and four sentences were hand-adjusted afterwards (create-dbandprisma platformreferences in the v6/v7 CLI reference). Re-running the script is now a no-op.orm/v6orm/v7guidesaccelerateaiconsoleF. Housekeeping
.claude/CLAUDE.md: the "Version Fallback" paragraph (and thecontent/docs.v6/structure bullets and the stalesrc/app/docs/[version]/…route path) now describe reality — versions are content folders, there is no fallback, and the metadata/canonical helpers are named.src/components/version-banner.tsx(nothing imported it).src/lib/content-metadata.test.ts(new): fails if two pages in the same version tree sharemetaTitle+metaDescription, and if any page is missing either. Twins across trees stay allowed because Part A differentiates them at render time.How it was tested
pnpm check(includes the new tests): green — 29 tests inapps/docs, 31 in the repo.pnpm types:check:docs,blog,eclipse,@prisma/eclipse,@prisma-docs/uigreen.site#types:checkfails in this sandbox withDOCS_ORIGIN is required in production— reproduced on unmodifiedmain, andapps/siteis untouched here.@prisma-docs/uialso needspnpm --filter @prisma/eclipse buildfirst in a fresh clone.pnpm lint:links: 0 errors, now including same-page fragments. Verified the new check actually fails by reintroducing the stale bookmark.pnpm --filter docs run lint:spellcheck: 827 files, 0 issues.apps/docsunit tests cover the helpers directly: 16 forversion-metadata, 4 forcanonical, 2 content invariants.next dev/next buildforapps/docsorapps/blog(they OOM in the sandbox, per the task), so the badge placement and rendered<head>were verified by reading the source, not in a browser.Assumptions and choices
Applies to Prisma ORM v6.) rather than a parenthetical, so it reads naturally when Google truncates it./orm/v6/→/orm/v7/normalisation. Every one of the 12 pairs was diffed; all differences were version paths only. Nothing that differs in substance was consolidated.testscript now runsnode --import tsx --testinstead of barenode --test: without the loader, a test cannot import a module that uses extensionless relative imports (version-metadata.ts→./version).tsxis already a devDependency and the gate is unchanged in what it asserts.canonicalfrontmatter value is a docs-relative path; absolute URLs also work.Follow-ups not done here (out of scope)
src/app/(docs)/sitemap.ts); it should probably drop or deprioritise pages with a foreign canonical.scripts/find-version-twin-duplicates.ts --checkis not wired into a gate. It would catch a v6 page that drifts away from its twin while keeping a canonical, but content edits would then fail the build, which deserves its own decision.pnpm check(oxfmt) reformats two unrelatedapps/blogfiles on a clean checkout ofmain; reverted here to keep this diff scoped.Errorson botherror-referencepages, one per tree); they are unique within a tree, so the new test passes, but they are weak titles.