From a833fe599304b29e0ee0ea3501a1747ef4890ac6 Mon Sep 17 00:00:00 2001 From: Honey Tyagi <78690656+HoneyTyagii@users.noreply.github.com> Date: Fri, 10 Jul 2026 16:00:05 +0530 Subject: [PATCH 1/4] docs: fix grammar in slug front matter descriptions (#12261) --- website/docs/api/plugins/plugin-content-blog.mdx | 2 +- website/docs/api/plugins/plugin-content-docs.mdx | 2 +- website/docs/api/plugins/plugin-content-pages.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/website/docs/api/plugins/plugin-content-blog.mdx b/website/docs/api/plugins/plugin-content-blog.mdx index 23cccd58bd39..9f4c3d8f5752 100644 --- a/website/docs/api/plugins/plugin-content-blog.mdx +++ b/website/docs/api/plugins/plugin-content-blog.mdx @@ -263,7 +263,7 @@ Accepted fields: | `keywords` | `string[]` | `undefined` | Keywords meta tag, which will become the `` in ``, used by search engines. | | `description` | `string` | The first line of Markdown content | The description of your document, which will become the `` and `` in ``, used by search engines. | | `image` | `string` | `undefined` | Cover or thumbnail image that will be used as the `` in the ``, enhancing link previews on social media and messaging platforms. | -| `slug` | `string` | File path | Allows to customize the blog post URL (`//`). Support multiple patterns: `slug: my-blog-post`, `slug: /my/path/to/blog/post`, slug: `/`. | +| `slug` | `string` | File path | Allows to customize the blog post URL (`//`). Supports multiple patterns: `slug: my-blog-post`, `slug: /my/path/to/blog/post`, slug: `/`. | | `last_update` | `FrontMatterLastUpdate` | `undefined` | Allows overriding the last update author/date. Date can be any [parsable date string](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse). | ```mdx-code-block diff --git a/website/docs/api/plugins/plugin-content-docs.mdx b/website/docs/api/plugins/plugin-content-docs.mdx index 38ef345599e5..59379c1eed4d 100644 --- a/website/docs/api/plugins/plugin-content-docs.mdx +++ b/website/docs/api/plugins/plugin-content-docs.mdx @@ -296,7 +296,7 @@ Accepted fields: | `keywords` | `string[]` | `undefined` | Keywords meta tag for the document page, for search engines. | | `description` | `string` | The first line of Markdown content | The description of your document, which will become the `` and `` in ``, used by search engines. | | `image` | `string` | `undefined` | Cover or thumbnail image that will be used as the `` in the ``, enhancing link previews on social media and messaging platforms. | -| `slug` | `string` | File path | Allows to customize the document URL (`//`). Support multiple patterns: `slug: my-doc`, `slug: /my/path/myDoc`, `slug: /`. | +| `slug` | `string` | File path | Allows to customize the document URL (`//`). Supports multiple patterns: `slug: my-doc`, `slug: /my/path/myDoc`, `slug: /`. | | `tags` | `Tag[]` | `undefined` | A list of strings or objects of two string fields `label` and `permalink` to tag to your docs. Strings can be a reference to keys of a [tags file](#tags-file) (usually `tags.yml`) | | `draft` | `boolean` | `false` | Draft documents will only be available during development. | | `unlisted` | `boolean` | `false` | Unlisted documents will be available in both development and production. They will be "hidden" in production, not indexed, excluded from sitemaps, and can only be accessed by users having a direct link. | diff --git a/website/docs/api/plugins/plugin-content-pages.mdx b/website/docs/api/plugins/plugin-content-pages.mdx index 5a6d41bd0132..aff3d0c2531a 100644 --- a/website/docs/api/plugins/plugin-content-pages.mdx +++ b/website/docs/api/plugins/plugin-content-pages.mdx @@ -113,7 +113,7 @@ Accepted fields: | `description` | `string` | The first line of Markdown content | The description of your page, which will become the `` and `` in ``, used by search engines. | | `keywords` | `string[]` | `undefined` | Keywords meta tag, which will become the `` in ``, used by search engines. | | `image` | `string` | `undefined` | Cover or thumbnail image that will be used as the `` in the ``, enhancing link previews on social media and messaging platforms. | -| `slug` | `string` | File path | Allows to customize the page URL (`//`). Support multiple patterns: `slug: my-page`, `slug: /my/page`, slug: `/`. | +| `slug` | `string` | File path | Allows to customize the page URL (`//`). Supports multiple patterns: `slug: my-page`, `slug: /my/page`, slug: `/`. | | `wrapperClassName` | `string` | | Class name to be added to the wrapper element to allow targeting specific page content. | | `hide_table_of_contents` | `boolean` | `false` | Whether to hide the table of contents to the right. | | `draft` | `boolean` | `false` | Draft pages will only be available during development. | From 90ea7110c28409d29204debec3f72665c074d317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Francisco=20Mar=C3=ADn?= <50973629+franmc01@users.noreply.github.com> Date: Fri, 10 Jul 2026 05:30:45 -0500 Subject: [PATCH 2/4] docs: fix versionPersistence default value in theme config table (#12258) --- website/docs/api/themes/theme-configuration.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/api/themes/theme-configuration.mdx b/website/docs/api/themes/theme-configuration.mdx index 62adb1f63ecf..2b1e0cab2a5b 100644 --- a/website/docs/api/themes/theme-configuration.mdx +++ b/website/docs/api/themes/theme-configuration.mdx @@ -170,7 +170,7 @@ Our [main themes](./overview.mdx) offer additional theme configuration options f | Name | Type | Default | Description | | --- | --- | --- | --- | -| `versionPersistence` | `'localStorage' \| 'none'` | `undefined` | Defines the browser persistence of the preferred docs version. | +| `versionPersistence` | `'localStorage' \| 'none'` | `'localStorage'` | Defines the browser persistence of the preferred docs version. | | `sidebar.hideable` | `boolean` | `false` | Show a hide button at the bottom of the sidebar. | | `sidebar.autoCollapseCategories` | `boolean` | `false` | Automatically collapse all sibling categories of the one you navigate to. | From 625ec4aec69935b369eec8a00a33a54073194f95 Mon Sep 17 00:00:00 2001 From: spokodev Date: Fri, 10 Jul 2026 11:52:08 +0100 Subject: [PATCH 3/4] fix(utils-common): keep dollar signs when applying trailing slash (#12219) Co-authored-by: sebastien --- .../src/__tests__/applyTrailingSlash.test.ts | 9 +++++++++ .../docusaurus-utils-common/src/applyTrailingSlash.ts | 3 ++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/docusaurus-utils-common/src/__tests__/applyTrailingSlash.test.ts b/packages/docusaurus-utils-common/src/__tests__/applyTrailingSlash.test.ts index 629a4c49b2ee..64198f9190c6 100644 --- a/packages/docusaurus-utils-common/src/__tests__/applyTrailingSlash.test.ts +++ b/packages/docusaurus-utils-common/src/__tests__/applyTrailingSlash.test.ts @@ -90,6 +90,15 @@ describe('applyTrailingSlash', () => { expect(applyTrailingSlash('/abc/', params(undefined))).toBe('/abc/'); }); + it('keeps dollar signs in the pathname', () => { + // Regression test for edge case bug + // see https://github.com/facebook/docusaurus/pull/12219 + expect(applyTrailingSlash('/docs/a$$b', params(true))).toBe('/docs/a$$b/'); + expect(applyTrailingSlash('/docs/a$&b', params(true))).toBe('/docs/a$&b/'); + expect(applyTrailingSlash("/docs/a$'b", params(true))).toBe("/docs/a$'b/"); + expect(applyTrailingSlash('/docs/a$$b/', params(false))).toBe('/docs/a$$b'); + }); + it('applies to path with #anchor', () => { expect(applyTrailingSlash('/abc#anchor', params(true))).toBe( '/abc/#anchor', diff --git a/packages/docusaurus-utils-common/src/applyTrailingSlash.ts b/packages/docusaurus-utils-common/src/applyTrailingSlash.ts index f83e5ce9c076..58e5141fa4e8 100644 --- a/packages/docusaurus-utils-common/src/applyTrailingSlash.ts +++ b/packages/docusaurus-utils-common/src/applyTrailingSlash.ts @@ -40,6 +40,7 @@ export default function applyTrailingSlash( // The trailing slash should be handled before the ?search#hash ! const [pathname] = path.split(/[#?]/) as [string, ...string[]]; + const queryHash = path.slice(pathname.length); // Never transform '/' to '' // Never remove the baseUrl trailing slash! @@ -51,7 +52,7 @@ export default function applyTrailingSlash( ? pathname : handleTrailingSlash(pathname, trailingSlash); - return path.replace(pathname, newPathname); + return `${newPathname}${queryHash}`; } /** Appends a leading slash to `str`, if one doesn't exist. */ From 54556428dc3d038aafca1ca58694c04081829e92 Mon Sep 17 00:00:00 2001 From: Aaron Thomas <158775699+ATKasem@users.noreply.github.com> Date: Fri, 10 Jul 2026 07:24:56 -0500 Subject: [PATCH 4/4] fix(blog, docs): apply trailingSlash to blog structured data URLs (#12262) Co-authored-by: sebastien --- .../src/client/structuredDataUtils.ts | 20 ++++++++++--- .../src/client/structuredDataUtils.ts | 28 +++++++++++++++++-- .../src/applyTrailingSlash.ts | 5 +++- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/packages/docusaurus-plugin-content-blog/src/client/structuredDataUtils.ts b/packages/docusaurus-plugin-content-blog/src/client/structuredDataUtils.ts index a541fc7705c7..550e25d2c543 100644 --- a/packages/docusaurus-plugin-content-blog/src/client/structuredDataUtils.ts +++ b/packages/docusaurus-plugin-content-blog/src/client/structuredDataUtils.ts @@ -8,6 +8,7 @@ import {useBaseUrlUtils, type BaseUrlUtils} from '@docusaurus/useBaseUrl'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import {useBlogMetadata} from '@docusaurus/plugin-content-blog/client'; +import {applyTrailingSlash} from '@docusaurus/utils-common'; import type {Props as BlogListPageStructuredDataProps} from '@theme/BlogListPage/StructuredData'; import {useBlogPost} from './contexts'; @@ -26,6 +27,17 @@ import type {DocusaurusConfig} from '@docusaurus/types'; const convertDate = (dateMs: number) => new Date(dateMs).toISOString(); +function getAbsoluteUrl( + permalink: string, + siteConfig: DocusaurusConfig, +): string { + const absoluteUrl = `${siteConfig.url}${permalink}`; + return applyTrailingSlash(absoluteUrl, { + trailingSlash: siteConfig.trailingSlash, + baseUrl: siteConfig.baseUrl, + }); +} + function getBlogPost( blogPostContent: PropBlogPostContent, siteConfig: DocusaurusConfig, @@ -37,7 +49,7 @@ function getBlogPost( const image = assets.image ?? frontMatter.image; const keywords = frontMatter.keywords ?? []; - const blogUrl = `${siteConfig.url}${metadata.permalink}`; + const blogUrl = getAbsoluteUrl(metadata.permalink, siteConfig); const dateModified = lastUpdatedAt ? convertDate(lastUpdatedAt) : undefined; @@ -92,7 +104,7 @@ export function useBlogListPageStructuredData( metadata: {blogDescription, blogTitle, permalink}, } = props; - const url = `${siteConfig.url}${permalink}`; + const url = getAbsoluteUrl(permalink, siteConfig); // details on structured data support: https://schema.org/Blog return { @@ -121,7 +133,7 @@ export function useBlogPostStructuredData(): WithContext { const dateModified = lastUpdatedAt ? convertDate(lastUpdatedAt) : undefined; - const url = `${siteConfig.url}${metadata.permalink}`; + const url = getAbsoluteUrl(metadata.permalink, siteConfig); // details on structured data support: https://schema.org/BlogPosting // BlogPosting is one of the structured data types that Google explicitly @@ -142,7 +154,7 @@ export function useBlogPostStructuredData(): WithContext { ...(keywords ? {keywords} : {}), isPartOf: { '@type': 'Blog', - '@id': `${siteConfig.url}${blogMetadata.blogBasePath}`, + '@id': getAbsoluteUrl(blogMetadata.blogBasePath, siteConfig), name: blogMetadata.blogTitle, }, }; diff --git a/packages/docusaurus-plugin-content-docs/src/client/structuredDataUtils.ts b/packages/docusaurus-plugin-content-docs/src/client/structuredDataUtils.ts index 01367a43881e..806353346313 100644 --- a/packages/docusaurus-plugin-content-docs/src/client/structuredDataUtils.ts +++ b/packages/docusaurus-plugin-content-docs/src/client/structuredDataUtils.ts @@ -6,15 +6,28 @@ */ import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import {applyTrailingSlash} from '@docusaurus/utils-common'; import type {PropSidebarBreadcrumbsItem} from '@docusaurus/plugin-content-docs'; import type {WithContext, BreadcrumbList} from 'schema-dts'; +import type {DocusaurusConfig} from '@docusaurus/types'; -export function useBreadcrumbsStructuredData({ +type Params = Pick; + +function getAbsoluteUrl(permalink: string, params: Params): string { + const absoluteUrl = `${params.url}${permalink}`; + return applyTrailingSlash(absoluteUrl, { + trailingSlash: params.trailingSlash, + baseUrl: params.baseUrl, + }); +} + +function getBreadcrumbsStructuredData({ breadcrumbs, + params, }: { breadcrumbs: PropSidebarBreadcrumbsItem[]; + params: Params; }): WithContext { - const {siteConfig} = useDocusaurusContext(); return { '@context': 'https://schema.org', '@type': 'BreadcrumbList', @@ -26,7 +39,16 @@ export function useBreadcrumbsStructuredData({ '@type': 'ListItem', position: index + 1, name: breadcrumb.label, - item: `${siteConfig.url}${breadcrumb.href}`, + item: getAbsoluteUrl(breadcrumb.href!, params), })), }; } + +export function useBreadcrumbsStructuredData({ + breadcrumbs, +}: { + breadcrumbs: PropSidebarBreadcrumbsItem[]; +}): WithContext { + const {siteConfig} = useDocusaurusContext(); + return getBreadcrumbsStructuredData({breadcrumbs, params: siteConfig}); +} diff --git a/packages/docusaurus-utils-common/src/applyTrailingSlash.ts b/packages/docusaurus-utils-common/src/applyTrailingSlash.ts index 58e5141fa4e8..6e8b63fd4365 100644 --- a/packages/docusaurus-utils-common/src/applyTrailingSlash.ts +++ b/packages/docusaurus-utils-common/src/applyTrailingSlash.ts @@ -17,7 +17,10 @@ export function addTrailingSlash(str: string): string { return str.endsWith('/') ? str : `${str}/`; } -// Trailing slash handling depends in some site configuration options +/** + * Apply/remove a trailing slash on an URL path according to site config options + * Usually applied on permalinks / URL paths, but also works with absolute URL + */ export default function applyTrailingSlash( path: string, options: ApplyTrailingSlashParams,