diff --git a/apps/web/src/components/ui/sidebar/SidebarScrollMemory.astro b/apps/web/src/components/ui/sidebar/SidebarScrollMemory.astro
new file mode 100644
index 000000000..fa0132f3f
--- /dev/null
+++ b/apps/web/src/components/ui/sidebar/SidebarScrollMemory.astro
@@ -0,0 +1,76 @@
+---
+/**
+ * Remembers the documentation sidebar rail's scroll position across full-page
+ * navigations and reveals the active entry on a cold load — so following a link
+ * never dumps the reader back at the top of a long tree.
+ *
+ * Docs and the API Reference share one scrolling rail (owned by `DocShell`) but
+ * render different trees, so each variant passes its own `storageKey` to keep
+ * their saved offsets apart. It targets the rail via `data-doc-sidebar-rail`,
+ * set by `DocShell`.
+ *
+ * The script is intentionally `is:inline`: it must set `scrollTop` before the
+ * first paint to avoid a visible jump, which a bundled (deferred) module script
+ * cannot guarantee. `define:vars` injects `storageKey` as a leading `const`.
+ */
+interface Props {
+ readonly storageKey: string
+}
+
+const { storageKey } = Astro.props
+---
+
+
diff --git a/apps/web/src/layouts/DocShell.astro b/apps/web/src/layouts/DocShell.astro
index 40a61e00c..ee39f5adf 100644
--- a/apps/web/src/layouts/DocShell.astro
+++ b/apps/web/src/layouts/DocShell.astro
@@ -1,6 +1,7 @@
---
import Footer from "@/components/Footer.astro"
import Navigation from "@/components/navigation/Navigation.astro"
+import SidebarScrollMemory from "@/components/ui/sidebar/SidebarScrollMemory.astro"
import BaseLayout from "@/layouts/BaseLayout.astro"
import { PAGE_TITLE_ID } from "@/lib/constants/skip-link"
import type { NavigationActiveSlug } from "@/lib/navigation"
@@ -48,7 +49,7 @@ const railClass = "sticky top-16 max-h-[calc(100vh-4rem)] overflow-y-auto px-6 p
{
hasSidebar && (