diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__snapshots__/index.test.ts.snap b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__snapshots__/index.test.ts.snap
index 22b6882eebe8..43c63ddfd74f 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__snapshots__/index.test.ts.snap
+++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/__snapshots__/index.test.ts.snap
@@ -3,17 +3,17 @@
exports[`transformLinks plugin > transform md links to 1`] = `
"[asset](https://example.com/asset.pdf)
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default} />
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default} />
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>asset
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>asset
-in paragraph /node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>asset
+in paragraph /node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>asset
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset (2).pdf").default}>asset with URL encoded chars
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset (2).pdf").default}>asset with URL encoded chars
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default + '#page=2'}>asset with hash
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default + '#page=2'}>asset with hash
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default} title="Title">asset
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default} title="Title">asset
[page](noUrl.md)
@@ -27,23 +27,23 @@ in paragraph /node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>asset
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>asset
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static2/asset2.pdf").default}>asset2
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static2/asset2.pdf").default}>asset2
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>staticAsset.pdf
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>staticAsset.pdf
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>@site/static/staticAsset.pdf
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>@site/static/staticAsset.pdf
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default + '#page=2'} title="Title">@site/static/staticAsset.pdf
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default + '#page=2'} title="Title">@site/static/staticAsset.pdf
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>Just staticAsset.pdf, and /node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>**awesome** staticAsset 2.pdf 'It is really "AWESOME"', but also /node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>coded \`staticAsset 3.pdf\`
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>Just staticAsset.pdf, and /node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>**awesome** staticAsset 2.pdf 'It is really "AWESOME"', but also /node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAsset.pdf").default}>coded \`staticAsset 3.pdf\`
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAssetImage.png").default}>
/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/staticAssetImage.png").default} width="200" height="200" />
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/staticAssetImage.png").default}>
/node_modules/url-loader/dist/cjs.js?limit=10000&name=assets/images/[name]-[contenthash].[ext]&fallback=/node_modules/file-loader/dist/cjs.js!./static/staticAssetImage.png").default} width="200" height="200" />
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>Stylized link to asset file
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./asset.pdf").default}>Stylized link to asset file
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./data.json").default}>JSON
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./data.json").default}>JSON
-/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/static-json.json").default}>static JSON"
+/node_modules/file-loader/dist/cjs.js?name=assets/files/[name]-[contenthash].[ext]!./static/static-json.json").default}>static JSON"
`;
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts
index e33ce8fc785b..0247a3145d70 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/__tests__/index.test.ts
@@ -62,6 +62,7 @@ describe('transformLinks plugin', () => {
it('transform md links to ', async () => {
// TODO split fixture in many smaller test cases
const result = await processFixture('asset');
+ expect(result).toContain('data-noTrailingSlash={true}');
expect(result).toMatchSnapshot();
});
diff --git a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts
index 2642880fa885..260855684480 100644
--- a/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts
+++ b/packages/docusaurus-mdx-loader/src/remark/transformLinks/index.ts
@@ -120,7 +120,7 @@ async function toAssetRequireNode(
});
// Assets are not routes, and are required by Webpack already
- // They should not trigger the broken link checker
+ // They should not trigger route-specific link handling
attributes.push({
type: 'mdxJsxAttribute',
name: 'data-noBrokenLinkCheck',
@@ -147,6 +147,31 @@ async function toAssetRequireNode(
},
});
+ attributes.push({
+ type: 'mdxJsxAttribute',
+ name: 'data-noTrailingSlash',
+ value: {
+ type: 'mdxJsxAttributeValueExpression',
+ value: 'true',
+ data: {
+ estree: {
+ type: 'Program',
+ body: [
+ {
+ type: 'ExpressionStatement',
+ expression: {
+ type: 'Literal',
+ value: true,
+ raw: 'true',
+ },
+ },
+ ],
+ sourceType: 'module',
+ comments: [],
+ },
+ },
+ },
+ });
attributes.push({
type: 'mdxJsxAttribute',
name: 'href',
diff --git a/packages/docusaurus-module-type-aliases/src/index.d.ts b/packages/docusaurus-module-type-aliases/src/index.d.ts
index 4ab1e21a54e4..0de14d3eaae2 100644
--- a/packages/docusaurus-module-type-aliases/src/index.d.ts
+++ b/packages/docusaurus-module-type-aliases/src/index.d.ts
@@ -183,6 +183,9 @@ declare module '@docusaurus/Link' {
/** Escape hatch in case broken links check doesn't make sense. */
readonly 'data-noBrokenLinkCheck'?: boolean;
+
+ /** Escape hatch for links that should preserve their exact URL. */
+ readonly 'data-noTrailingSlash'?: boolean;
};
export default function Link(props: Props): ReactNode;
}
diff --git a/packages/docusaurus/src/client/exports/Link.tsx b/packages/docusaurus/src/client/exports/Link.tsx
index 1fab775967ee..3634fc3782b7 100644
--- a/packages/docusaurus/src/client/exports/Link.tsx
+++ b/packages/docusaurus/src/client/exports/Link.tsx
@@ -35,6 +35,7 @@ function Link({
activeClassName,
isActive,
'data-noBrokenLinkCheck': noBrokenLinkCheck,
+ 'data-noTrailingSlash': noTrailingSlash,
autoAddBaseUrl = true,
...props
}: Props): ReactNode {
@@ -88,7 +89,7 @@ function Link({
targetLink = targetLink?.slice(1);
}
- if (targetLink && isInternal) {
+ if (targetLink && isInternal && !noTrailingSlash) {
targetLink = applyTrailingSlash(targetLink, {trailingSlash, baseUrl});
}
diff --git a/packages/docusaurus/src/client/exports/__tests__/Link.test.tsx b/packages/docusaurus/src/client/exports/__tests__/Link.test.tsx
index fce2c970b6a8..09f3c615815e 100644
--- a/packages/docusaurus/src/client/exports/__tests__/Link.test.tsx
+++ b/packages/docusaurus/src/client/exports/__tests__/Link.test.tsx
@@ -122,6 +122,24 @@ describe('', () => {
`);
});
+ it('does not add a trailing slash to an asset link', () => {
+ const {container} = render(
+ ,
+ {trailingSlash: true},
+ );
+ expect(container.firstElementChild).toMatchInlineSnapshot(`
+
+ `);
+ });
+
it("can render '/docs/intro/' with trailingSlash false", () => {
const {container} = render(, {
trailingSlash: false,