From 4b3b352cf37e79bf8d9c9ab00a799656daec068d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Mon, 1 Jun 2026 16:15:29 +0200 Subject: [PATCH 1/5] fix(utils): fix `extractLeadingEmoji()` edge cases (#12100) --- .../src/utils/__tests__/emojiUtils.test.ts | 61 +++++++++++++++++++ .../src/utils/emojiUtils.ts | 9 ++- project-words.txt | 2 + 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/packages/docusaurus-theme-common/src/utils/__tests__/emojiUtils.test.ts b/packages/docusaurus-theme-common/src/utils/__tests__/emojiUtils.test.ts index 8fdaa6ccc25d..9c762c857f51 100644 --- a/packages/docusaurus-theme-common/src/utils/__tests__/emojiUtils.test.ts +++ b/packages/docusaurus-theme-common/src/utils/__tests__/emojiUtils.test.ts @@ -64,4 +64,65 @@ describe('extractLeadingEmoji', () => { rest: 'Hello World 😀', }); }); + + it('does not extract single digit', () => { + expect(extractLeadingEmoji('1 Hello World')).toEqual({ + emoji: null, + rest: '1 Hello World', + }); + }); + + it('does not extract multiple digits', () => { + expect(extractLeadingEmoji('11 Hello World')).toEqual({ + emoji: null, + rest: '11 Hello World', + }); + }); + + it('extracts real keycap emoji (digit + VS16 + combining keycap)', () => { + // 1️⃣ is digit "1" followed by U+FE0F (Variation Selector-16) and + // U+20E3 (Combining Enclosing Keycap) — a multi-codepoint grapheme + // that should still be detected as emoji + expect(extractLeadingEmoji('1\u{FE0F}\u{20E3} Something')).toEqual({ + emoji: '1️⃣', + rest: ' Something', + }); + }); + + it('extracts VS16 emoji - ⚠️ (text-default + variation selector)', () => { + // ⚠ (U+26A0) has Emoji=true, Emoji_Presentation=false. With VS16 + // (U+FE0F) it becomes a multi-codepoint grapheme that IS an emoji + expect(extractLeadingEmoji('⚠\u{FE0F} Warning')).toEqual({ + emoji: '⚠\u{FE0F}', + rest: ' Warning', + }); + }); + + it('does not detect text-presentation symbols - ©', () => { + expect(extractLeadingEmoji('© 2026')).toEqual({ + emoji: null, + rest: '© 2026', + }); + }); + + it('does detect ©️', () => { + expect(extractLeadingEmoji('©️ 2026')).toEqual({ + emoji: '©️', + rest: ' 2026', + }); + }); + + it('does not detect text-presentation symbols - ™', () => { + expect(extractLeadingEmoji('™ Brand')).toEqual({ + emoji: null, + rest: '™ Brand', + }); + }); + + it('does not detect text-presentation symbols - ♠', () => { + expect(extractLeadingEmoji('♠ Cards')).toEqual({ + emoji: null, + rest: '♠ Cards', + }); + }); }); diff --git a/packages/docusaurus-theme-common/src/utils/emojiUtils.ts b/packages/docusaurus-theme-common/src/utils/emojiUtils.ts index c66cbfb3fa01..cfa1cbde81c0 100644 --- a/packages/docusaurus-theme-common/src/utils/emojiUtils.ts +++ b/packages/docusaurus-theme-common/src/utils/emojiUtils.ts @@ -29,11 +29,10 @@ export function extractLeadingEmoji(input: string): { return {emoji: null, rest: input}; } - // Leading grapheme contains an emoji (covers flags/ZWJ/skin tones) - if ( - !/\p{Extended_Pictographic}/u.test(grapheme) && - !/\p{Emoji}/u.test(grapheme) - ) { + // Leading grapheme contains an emoji + // Covers flags/ZWJ/skin tones, excludes digits + // See https://github.com/facebook/docusaurus/pull/12072 + if (!/^\p{RGI_Emoji}$/v.test(grapheme)) { return {emoji: null, rest: input}; } diff --git a/project-words.txt b/project-words.txt index 600eddac630a..06190d4312f4 100644 --- a/project-words.txt +++ b/project-words.txt @@ -133,6 +133,8 @@ Kaszubowski Katex katex Kato +Keycap +keycap Keytar keytar Kinsta From 9cb6a710e2cbae2c38f00a1d67adf445c76a37f9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2026 16:37:07 +0200 Subject: [PATCH 2/5] chore(deps-dev): bump strip-ansi from 6.0.1 to 7.2.0 (#12059) Signed-off-by: dependabot[bot] --- package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6f7d9b635872..0f54bee167db 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,7 @@ "react-dom": "^19.2.5", "rimraf": "^3.0.2", "sharp": "^0.34.5", - "strip-ansi": "^6.0.1", + "strip-ansi": "^7.2.0", "stylelint": "^17.12.0", "stylelint-config-standard": "^40.0.0", "syncpack": "^14.3.1", diff --git a/yarn.lock b/yarn.lock index 01a0bc2f3fa4..9e7a63025a30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17350,7 +17350,7 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.1, strip-ansi@^7.1.0, strip-ansi@^7.1.2: +strip-ansi@^7.0.1, strip-ansi@^7.1.0, strip-ansi@^7.1.2, strip-ansi@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.2.0.tgz#d22a269522836a627af8d04b5c3fd2c7fa3e32e3" integrity sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w== From b4df481859946059389571fa6b263c2477d58a1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Mon, 1 Jun 2026 16:39:36 +0200 Subject: [PATCH 3/5] break(theme): `@docusaurus/theme-common` doesn't export `` anymore (AI-assisted) (#12086) --- .../src/theme/ContentVisibility/Unlisted/index.tsx | 10 +++++++++- packages/docusaurus-theme-common/src/index.ts | 1 - .../translations/contentVisibilityTranslations.tsx | 11 ----------- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/docusaurus-theme-classic/src/theme/ContentVisibility/Unlisted/index.tsx b/packages/docusaurus-theme-classic/src/theme/ContentVisibility/Unlisted/index.tsx index 2a116bff5289..14d0e81fbd72 100644 --- a/packages/docusaurus-theme-classic/src/theme/ContentVisibility/Unlisted/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/ContentVisibility/Unlisted/index.tsx @@ -7,15 +7,23 @@ import React, {type ReactNode} from 'react'; import clsx from 'clsx'; +import Head from '@docusaurus/Head'; import { ThemeClassNames, UnlistedBannerTitle, UnlistedBannerMessage, - UnlistedMetadata, } from '@docusaurus/theme-common'; import Admonition from '@theme/Admonition'; import type {Props} from '@theme/ContentVisibility/Unlisted'; +function UnlistedMetadata(): ReactNode { + return ( + + + + ); +} + function UnlistedBanner({className}: Props) { return ( - - - ); -} - export function DraftBannerTitle(): ReactNode { return ( Date: Mon, 1 Jun 2026 16:59:35 +0200 Subject: [PATCH 4/5] refactor(ssg): Remove SSG workaround for React 18 null warning bug (#12102) --- packages/docusaurus/src/ssg/ssgGlobalResult.ts | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/packages/docusaurus/src/ssg/ssgGlobalResult.ts b/packages/docusaurus/src/ssg/ssgGlobalResult.ts index d23c092b98cb..cdf671951f31 100644 --- a/packages/docusaurus/src/ssg/ssgGlobalResult.ts +++ b/packages/docusaurus/src/ssg/ssgGlobalResult.ts @@ -25,22 +25,11 @@ function printSSGWarnings(results: SSGSuccess[]): void { return; } - const ignoredWarnings: string[] = [ - // TODO Docusaurus v4: remove with React 19 upgrade - // React 18 emit NULL chars, and minifier detects it - // see https://github.com/facebook/docusaurus/issues/9985 - 'Unexpected null character', - ]; - - const keepWarning = (warning: string) => { - return !ignoredWarnings.some((iw) => warning.includes(iw)); - }; - const resultsWithWarnings = results .map((success) => { return { ...success, - warnings: success.result.warnings.filter(keepWarning), + warnings: success.result.warnings, }; }) .filter((result) => result.warnings.length > 0); From 183fc6f1e3d5dab9fb9687e85091660f90a42010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Lorber?= Date: Mon, 1 Jun 2026 17:48:09 +0200 Subject: [PATCH 5/5] refactor: various v4 code cleanups and soft breaking changes (#12104) --- .../src/theme-classic.d.ts | 6 ---- .../src/theme/CodeBlock/Content/index.tsx | 25 ++++++-------- .../Navbar/MobileSidebar/Layout/index.tsx | 16 ++------- packages/docusaurus-theme-common/package.json | 1 - packages/docusaurus-theme-common/src/index.ts | 34 ------------------- .../docusaurus-theme-common/src/internal.ts | 8 ----- .../src/utils/codeBlockUtils.tsx | 6 ---- .../src/client/index.ts | 16 ++------- packages/docusaurus-utils/src/index.ts | 7 ---- .../docusaurus/src/client/exports/Link.tsx | 27 +++++++-------- .../commands/utils/openBrowser/openBrowser.ts | 15 +++----- packages/docusaurus/src/server/i18n.ts | 6 +--- packages/docusaurus/src/ssg/ssgEnv.ts | 5 ++- .../with-category-name-doc.mdx | 2 +- .../tests/sidebar-frontmatter/index.mdx | 2 +- website/src/components/APITable/index.tsx | 22 ++++++------ 16 files changed, 49 insertions(+), 149 deletions(-) diff --git a/packages/docusaurus-theme-classic/src/theme-classic.d.ts b/packages/docusaurus-theme-classic/src/theme-classic.d.ts index 644511d7d881..d1d80a5289a3 100644 --- a/packages/docusaurus-theme-classic/src/theme-classic.d.ts +++ b/packages/docusaurus-theme-classic/src/theme-classic.d.ts @@ -1324,9 +1324,6 @@ declare module '@theme/NavbarItem/DefaultNavbarItem' { readonly position?: 'left' | 'right'; }; - // TODO Docusaurus v4, remove old type name - export type DesktopOrMobileNavBarItemProps = DefaultNavbarItemProps; - export interface Props extends DefaultNavbarItemProps { readonly mobile?: boolean; } @@ -1380,9 +1377,6 @@ declare module '@theme/NavbarItem/DropdownNavbarItem' { readonly className?: string; }; - // TODO Docusaurus v4, remove old type name - export type DesktopOrMobileNavBarItemProps = DropdownNavbarItemProps; - export interface Props extends DropdownNavbarItemProps { readonly mobile?: boolean; } diff --git a/packages/docusaurus-theme-classic/src/theme/CodeBlock/Content/index.tsx b/packages/docusaurus-theme-classic/src/theme/CodeBlock/Content/index.tsx index ffc45fc87b12..7d73a11f812d 100644 --- a/packages/docusaurus-theme-classic/src/theme/CodeBlock/Content/index.tsx +++ b/packages/docusaurus-theme-classic/src/theme/CodeBlock/Content/index.tsx @@ -15,20 +15,17 @@ import Line from '@theme/CodeBlock/Line'; import styles from './styles.module.css'; -// TODO Docusaurus v4: remove useless forwardRef -const Pre = React.forwardRef>( - function Pre(props, ref) { - return ( -
-    );
-  },
-);
+function Pre({className, ref, ...props}: ComponentProps<'pre'>) {
+  return (
+    
+  );
+}
 
 function Code(props: ComponentProps<'code'>) {
   const {metadata} = useCodeBlockContext();
diff --git a/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/Layout/index.tsx b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/Layout/index.tsx
index 29e705b220e1..1bea082075d2 100644
--- a/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/Layout/index.tsx
+++ b/packages/docusaurus-theme-classic/src/theme/Navbar/MobileSidebar/Layout/index.tsx
@@ -5,24 +5,12 @@
  * LICENSE file in the root directory of this source tree.
  */
 
-import React, {version, type ReactNode} from 'react';
+import React, {type ReactNode} from 'react';
 import clsx from 'clsx';
 import {useNavbarSecondaryMenu} from '@docusaurus/theme-common/internal';
 import {ThemeClassNames} from '@docusaurus/theme-common';
 import type {Props} from '@theme/Navbar/MobileSidebar/Layout';
 
-// TODO Docusaurus v4: remove temporary inert workaround
-//  See https://github.com/facebook/react/issues/17157
-//  See https://github.com/radix-ui/themes/pull/509
-function inertProps(inert: boolean) {
-  const isBeforeReact19 = parseInt(version!.split('.')[0]!, 10) < 19;
-  if (isBeforeReact19) {
-    // TODO Docusaurus v4: remove temporary inert workaround
-    return {inert: inert ? '' : undefined} as unknown as {inert: boolean};
-  }
-  return {inert};
-}
-
 function NavbarMobileSidebarPanel({
   children,
   inert,
@@ -36,7 +24,7 @@ function NavbarMobileSidebarPanel({
         ThemeClassNames.layout.navbar.mobileSidebar.panel,
         'navbar-sidebar__item menu',
       )}
-      {...inertProps(inert)}>
+      inert={inert}>
       {children}
     
   );
diff --git a/packages/docusaurus-theme-common/package.json b/packages/docusaurus-theme-common/package.json
index 01b3028a4911..cde98294dc37 100644
--- a/packages/docusaurus-theme-common/package.json
+++ b/packages/docusaurus-theme-common/package.json
@@ -51,7 +51,6 @@
     "lodash": "^4.17.21"
   },
   "peerDependencies": {
-    "@docusaurus/plugin-content-docs": "*",
     "react": "^19.2.0",
     "react-dom": "^19.2.0"
   },
diff --git a/packages/docusaurus-theme-common/src/index.ts b/packages/docusaurus-theme-common/src/index.ts
index 9962acfda8f2..1d239ec048c6 100644
--- a/packages/docusaurus-theme-common/src/index.ts
+++ b/packages/docusaurus-theme-common/src/index.ts
@@ -5,40 +5,6 @@
  * LICENSE file in the root directory of this source tree.
  */
 
-import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
-import {DEFAULT_SEARCH_TAG} from './utils/searchUtils';
-
-// TODO Docusaurus v4: remove these workarounds as a breaking change
-//  and remove docs plugin peerDeps in theme-common/package.json
-//  This is public API surface that we need to keep for v3
-//  See https://github.com/facebook/docusaurus/pull/10316
-export function useCurrentSidebarCategory(...args: unknown[]): unknown {
-  // eslint-disable-next-line @typescript-eslint/no-require-imports
-  return require('@docusaurus/plugin-content-docs/client').useCurrentSidebarCategory(
-    ...args,
-  );
-}
-export function filterDocCardListItems(...args: unknown[]): unknown {
-  // eslint-disable-next-line @typescript-eslint/no-require-imports
-  return require('@docusaurus/plugin-content-docs/client').filterDocCardListItems(
-    ...args,
-  );
-}
-export function useDocsPreferredVersion(...args: unknown[]): unknown {
-  // eslint-disable-next-line @typescript-eslint/no-require-imports
-  return require('@docusaurus/plugin-content-docs/client').useDocsPreferredVersion(
-    ...args,
-  );
-}
-export function useContextualSearchFilters() {
-  const {i18n} = useDocusaurusContext();
-  const docsTags =
-    // eslint-disable-next-line @typescript-eslint/no-require-imports
-    require('@docusaurus/plugin-content-docs/client').useDocsContextualSearchTags();
-  const tags = [DEFAULT_SEARCH_TAG, ...docsTags];
-  return {locale: i18n.currentLocale, tags};
-}
-
 /*
  * APIs to document
  */
diff --git a/packages/docusaurus-theme-common/src/internal.ts b/packages/docusaurus-theme-common/src/internal.ts
index 02c5776173d1..a12f43e8eb4e 100644
--- a/packages/docusaurus-theme-common/src/internal.ts
+++ b/packages/docusaurus-theme-common/src/internal.ts
@@ -44,14 +44,6 @@ export {
   getPrismCssVariables,
   CodeBlockContextProvider,
   useCodeBlockContext,
-
-  // TODO Docusaurus v4: remove, only kept for internal retro-compatibility
-  //  See https://github.com/facebook/docusaurus/pull/11153
-  parseCodeBlockTitle,
-  parseClassNameLanguage as parseLanguage,
-  parseLines,
-  getLineNumbersStart,
-  containsLineNumbers,
 } from './utils/codeBlockUtils';
 
 export {DEFAULT_SEARCH_TAG} from './utils/searchUtils';
diff --git a/packages/docusaurus-theme-common/src/utils/codeBlockUtils.tsx b/packages/docusaurus-theme-common/src/utils/codeBlockUtils.tsx
index fc18e4d44b57..914fc5de3557 100644
--- a/packages/docusaurus-theme-common/src/utils/codeBlockUtils.tsx
+++ b/packages/docusaurus-theme-common/src/utils/codeBlockUtils.tsx
@@ -188,12 +188,6 @@ export function getLineNumbersStart({
   return getMetaLineNumbersStart(metastring);
 }
 
-// TODO Docusaurus v4: remove, only kept for internal retro-compatibility
-//  See https://github.com/facebook/docusaurus/pull/11153
-export function containsLineNumbers(metastring?: string): boolean {
-  return Boolean(metastring?.includes('showLineNumbers'));
-}
-
 type ParseCodeLinesParam = {
   /**
    * The full metastring, as received from MDX. Line ranges declared here
diff --git a/packages/docusaurus-theme-mermaid/src/client/index.ts b/packages/docusaurus-theme-mermaid/src/client/index.ts
index ebb57f4ace43..8113e8e8fd1f 100644
--- a/packages/docusaurus-theme-mermaid/src/client/index.ts
+++ b/packages/docusaurus-theme-mermaid/src/client/index.ts
@@ -5,7 +5,7 @@
  * LICENSE file in the root directory of this source tree.
  */
 
-import {useState, useEffect, useMemo} from 'react';
+import {useState, useEffect, useMemo, useId} from 'react';
 import {useColorMode, useThemeConfig} from '@docusaurus/theme-common';
 import {loadMermaid} from './loadMermaid';
 
@@ -32,19 +32,9 @@ export function useMermaidConfig(): MermaidConfig {
   );
 }
 
+// Random client-only id, we don't care much but mermaid want an id so...
 function useMermaidId(): string {
-  /*
-  Random client-only id, we don't care much but mermaid want an id so...
-  Note: Mermaid doesn't like values provided by Rect.useId() and throws
-  */
-
-  // TODO Docusaurus v4: check if useId() now works
-  //  It could work thanks to https://github.com/facebook/react/pull/32001
-  // return useId(); // tried that, doesn't work ('#d:re:' is not a valid selector.)
-
-  return useState(
-    () => `mermaid-svg-${Math.round(Math.random() * 10000000)}`,
-  )[0];
+  return useId();
 }
 
 async function renderMermaid({
diff --git a/packages/docusaurus-utils/src/index.ts b/packages/docusaurus-utils/src/index.ts
index 815b4bbc9070..ff097e3e6265 100644
--- a/packages/docusaurus-utils/src/index.ts
+++ b/packages/docusaurus-utils/src/index.ts
@@ -24,13 +24,6 @@ export {
   WEBPACK_URL_LOADER_LIMIT,
 } from './constants';
 export {generate, readOutputHTMLFile} from './emitUtils';
-export {
-  // TODO Docusaurus v4: remove these legacy exports,
-  //  they are only kept for retro-compatibility
-  getFileCommitDate,
-  FileNotTrackedError,
-  GitNotFoundError,
-} from './vcs/gitUtils';
 export {
   mergeTranslations,
   updateTranslationFileMessages,
diff --git a/packages/docusaurus/src/client/exports/Link.tsx b/packages/docusaurus/src/client/exports/Link.tsx
index e294ecff7b70..1fab775967ee 100644
--- a/packages/docusaurus/src/client/exports/Link.tsx
+++ b/packages/docusaurus/src/client/exports/Link.tsx
@@ -28,19 +28,16 @@ import type {Props} from '@docusaurus/Link';
 // like "introduction" to "/baseUrl/introduction" => bad behavior to fix
 const shouldAddBaseUrlAutomatically = (to: string) => to.startsWith('/');
 
-function Link(
-  {
-    isNavLink,
-    to,
-    href,
-    activeClassName,
-    isActive,
-    'data-noBrokenLinkCheck': noBrokenLinkCheck,
-    autoAddBaseUrl = true,
-    ...props
-  }: Props,
-  forwardedRef: React.ForwardedRef,
-): ReactNode {
+function Link({
+  isNavLink,
+  to,
+  href,
+  activeClassName,
+  isActive,
+  'data-noBrokenLinkCheck': noBrokenLinkCheck,
+  autoAddBaseUrl = true,
+  ...props
+}: Props): ReactNode {
   const {siteConfig} = useDocusaurusContext();
   const {trailingSlash, baseUrl} = siteConfig;
   const router = siteConfig.future.experimental_router;
@@ -48,7 +45,7 @@ function Link(
   const brokenLinks = useBrokenLinks();
   const innerRef = useRef(null);
 
-  useImperativeHandle(forwardedRef, () => innerRef.current!);
+  useImperativeHandle(props.ref, () => innerRef.current!);
 
   // IMPORTANT: using to or href should not change anything
   // For example, MDX links will ALWAYS give us the href props
@@ -207,4 +204,4 @@ function Link(
   );
 }
 
-export default React.forwardRef(Link);
+export default Link;
diff --git a/packages/docusaurus/src/commands/utils/openBrowser/openBrowser.ts b/packages/docusaurus/src/commands/utils/openBrowser/openBrowser.ts
index 0660005a739c..e59e9d043227 100644
--- a/packages/docusaurus/src/commands/utils/openBrowser/openBrowser.ts
+++ b/packages/docusaurus/src/commands/utils/openBrowser/openBrowser.ts
@@ -72,16 +72,11 @@ async function tryOpenWithAppleScript({
         '|',
       )})$"`;
 
-      const result = await Promise
-        // TODO Docusaurus v4: use Promise.try()
-        // See why here https://github.com/facebook/docusaurus/issues/11204#issuecomment-3073480330
-        .resolve()
-        .then(() => execPromise(command))
-        .catch(() => {
-          // Ignore all errors
-          // In particular grep errors when macOS user has no Chromium-based browser open
-          // See https://github.com/facebook/docusaurus/issues/11204
-        });
+      const result = await Promise.try(() => execPromise(command)).catch(() => {
+        // Ignore all errors
+        // In particular grep errors when macOS user has no Chromium-based browser open
+        // See https://github.com/facebook/docusaurus/issues/11204
+      });
       if (!result) {
         return [];
       }
diff --git a/packages/docusaurus/src/server/i18n.ts b/packages/docusaurus/src/server/i18n.ts
index 81cd3728e8aa..959600b69448 100644
--- a/packages/docusaurus/src/server/i18n.ts
+++ b/packages/docusaurus/src/server/i18n.ts
@@ -79,11 +79,7 @@ function getDefaultDirection(localeStr: string) {
   const locale = new Intl.Locale(localeStr);
   // see https://github.com/tc39/proposal-intl-locale-info
   // see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getTextInfo
-  // Node 18.0 implements a former version of the getTextInfo() proposal
-  // TODO Docusaurus v4: remove the fallback to locale.textInfo
-  // @ts-expect-error: The TC39 proposal was updated
-  const textInto = locale.getTextInfo?.() ?? locale.textInfo;
-  return textInto.direction ?? 'ltr';
+  return locale.getTextInfo().direction ?? 'ltr';
 }
 
 export function getDefaultLocaleConfig(
diff --git a/packages/docusaurus/src/ssg/ssgEnv.ts b/packages/docusaurus/src/ssg/ssgEnv.ts
index 8927bb7c71dd..a3fc88ca5465 100644
--- a/packages/docusaurus/src/ssg/ssgEnv.ts
+++ b/packages/docusaurus/src/ssg/ssgEnv.ts
@@ -7,9 +7,8 @@
 
 // Secret way to set SSR plugin async concurrency option
 // Waiting for feedback before documenting this officially?
-// TODO Docusaurus v4, rename SSR => SSG
-export const SSGConcurrency = process.env.DOCUSAURUS_SSR_CONCURRENCY
-  ? parseInt(process.env.DOCUSAURUS_SSR_CONCURRENCY, 10)
+export const SSGConcurrency = process.env.DOCUSAURUS_SSG_CONCURRENCY
+  ? parseInt(process.env.DOCUSAURUS_SSG_CONCURRENCY, 10)
   : // Not easy to define a reasonable option default
     // Will still be better than Infinity
     // See also https://github.com/sindresorhus/p-map/issues/24
diff --git a/website/_dogfooding/_docs tests/tests/category-links/with-category-name-doc/with-category-name-doc.mdx b/website/_dogfooding/_docs tests/tests/category-links/with-category-name-doc/with-category-name-doc.mdx
index 67c89fc762c0..1febccc90d39 100644
--- a/website/_dogfooding/_docs tests/tests/category-links/with-category-name-doc/with-category-name-doc.mdx	
+++ b/website/_dogfooding/_docs tests/tests/category-links/with-category-name-doc/with-category-name-doc.mdx	
@@ -16,7 +16,7 @@ It is also possible to render the card items for that category thanks to MDX:
 
 ```mdx-code-block
 import DocCardList from '@theme/DocCardList';
-import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
+import {useCurrentSidebarCategory} from '@docusaurus/plugin-content-docs/client';
 
 
 ```
diff --git a/website/_dogfooding/_docs tests/tests/sidebar-frontmatter/index.mdx b/website/_dogfooding/_docs tests/tests/sidebar-frontmatter/index.mdx
index 3116679e175a..8dbf3762d5f6 100644
--- a/website/_dogfooding/_docs tests/tests/sidebar-frontmatter/index.mdx	
+++ b/website/_dogfooding/_docs tests/tests/sidebar-frontmatter/index.mdx	
@@ -7,7 +7,7 @@ sidebar_class_name: 'dogfood_sidebar_class_name_test'
 
 ```mdx-code-block
 import Link from '@docusaurus/Link';
-import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
+import {useCurrentSidebarCategory} from '@docusaurus/plugin-content-docs/client';
 import DocCardList from '@theme/DocCardList';
 
 
diff --git a/website/src/components/APITable/index.tsx b/website/src/components/APITable/index.tsx
index 29ad3e57881f..3c780740eeb3 100644
--- a/website/src/components/APITable/index.tsx
+++ b/website/src/components/APITable/index.tsx
@@ -42,13 +42,15 @@ function getRowName(node: ReactElement): string {
   return curNode as string;
 }
 
-function APITableRow(
-  {
-    name,
-    children,
-  }: {name: string | undefined; children: ReactElement>},
-  ref: React.ForwardedRef,
-) {
+function APITableRow({
+  name,
+  children,
+  ref,
+}: {
+  name: string | undefined;
+  children: ReactElement>;
+  ref: React.Ref;
+}) {
   const entryName = getRowName(children);
   const id = name ? `${name}-${entryName}` : entryName;
   const anchor = `#${id}`;
@@ -79,8 +81,6 @@ function APITableRow(
   );
 }
 
-const APITableRowComp = React.forwardRef(APITableRow);
-
 /*
  * Note: this is not a quite robust component since it makes a lot of
  * assumptions about how the children looks; however, those assumptions
@@ -104,9 +104,9 @@ export default function APITable({children, name}: Props): ReactNode {
     // @ts-expect-error: TODO fix typing
     tbody.props.children,
     (row: ReactElement>) => (
-      
+      
         {row}
-      
+      
     ),
   );