diff --git a/CHANGELOG.md b/CHANGELOG.md
index 97c754a86b..693b31c44a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@
 - Fix: ログアウトした際に処理が終了しない問題を修正
 - Fix: 自動バックアップが設定されている環境でログアウト直前に設定をバックアップするように
 - Fix: フォルダを開いた状態でメニューからアップロードしてもルートフォルダにアップロードされる問題を修正 #15836
+- Fix: タイムラインのスクロール位置を記憶するように修正
 
 ### Server
 - Enhance: フォローしているユーザーならフォロワー限定投稿のノートでもアンテナで検知できるように  
diff --git a/packages/frontend/src/use/use-scroll-position-keeper.ts b/packages/frontend/src/use/use-scroll-position-keeper.ts
new file mode 100644
index 0000000000..00cc51a459
--- /dev/null
+++ b/packages/frontend/src/use/use-scroll-position-keeper.ts
@@ -0,0 +1,58 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { throttle } from 'throttle-debounce';
+import { nextTick, onActivated, onUnmounted, watch } from 'vue';
+import type { Ref } from 'vue';
+
+// note render skippingがオンだとズレるため、遷移直前にスクロール範囲に表示されているdata-scroll-anchor要素を特定して、復元時に当該要素までスクロールするようにする
+
+export function useScrollPositionKeeper(scrollContainerRef: Ref<HTMLElement | null | undefined>): void {
+	let anchorId: string | null = null;
+
+	watch(scrollContainerRef, (el) => {
+		if (!el) return;
+
+		const onScroll = () => {
+			if (!el) return;
+			const scrollContainerRect = el.getBoundingClientRect();
+			const viewPosition = scrollContainerRect.height / 2;
+
+			const anchorEls = el.querySelectorAll('[data-scroll-anchor]');
+			for (let i = anchorEls.length - 1; i > -1; i--) { // 下から見た方が速い
+				const anchorEl = anchorEls[i] as HTMLElement;
+				const anchorRect = anchorEl.getBoundingClientRect();
+				const anchorTop = anchorRect.top;
+				const anchorBottom = anchorRect.bottom;
+				if (anchorTop <= viewPosition && anchorBottom >= viewPosition) {
+					anchorId = anchorEl.getAttribute('data-scroll-anchor');
+					break;
+				}
+			}
+		};
+
+		// ほんとはscrollイベントじゃなくてonBeforeDeactivatedでやりたい
+		// https://github.com/vuejs/vue/issues/9454
+		// https://github.com/vuejs/rfcs/pull/284
+		el.addEventListener('scroll', throttle(1000, onScroll), { passive: true });
+	}, {
+		immediate: true,
+	});
+
+	onActivated(() => {
+		nextTick(() => {
+			if (!anchorId) return;
+			const scrollContainer = scrollContainerRef.value;
+			if (!scrollContainer) return;
+			const scrollAnchorEl = scrollContainer.querySelector(`[data-scroll-anchor="${anchorId}"]`);
+			if (!scrollAnchorEl) return;
+			scrollAnchorEl.scrollIntoView({
+				behavior: 'instant',
+				block: 'center',
+				inline: 'center',
+			});
+		});
+	});
+}