Fix TOC showing wrong headings across navigation

Problem: Table of Contents displayed headings from previously viewed
articles when navigating between posts via client-side routing.

Root cause: PostToc component's useEffect with empty dependency array
only ran once on mount, so it retained stale heading data when React
reused the component instance during navigation.

Solution: Add contentKey prop flow:
- Blog/page routes pass slug to PostLayout
- PostLayout passes contentKey as key prop to PostToc instances
- React remounts PostToc when key changes, rebuilding TOC correctly

Files changed:
- components/post-layout.tsx: Add contentKey prop and key forwarding
- app/blog/[slug]/page.tsx: Pass slug as contentKey
- app/pages/[slug]/page.tsx: Pass slug as contentKey
This commit is contained in:
2025-11-20 15:57:47 +08:00
parent 3748e2f9e8
commit 2b1060dd45
3 changed files with 5 additions and 5 deletions

View File

@@ -79,7 +79,7 @@ export default async function BlogPostPage({ params }: Props) {
return ( return (
<> <>
<ReadingProgress /> <ReadingProgress />
<PostLayout hasToc={hasToc}> <PostLayout hasToc={hasToc} contentKey={slug}>
<div className="space-y-8"> <div className="space-y-8">
{/* Main content area for Pagefind indexing */} {/* Main content area for Pagefind indexing */}
<div data-pagefind-body> <div data-pagefind-body>

View File

@@ -42,7 +42,7 @@ export default async function StaticPage({ params }: Props) {
return ( return (
<> <>
<ReadingProgress /> <ReadingProgress />
<PostLayout hasToc={hasToc}> <PostLayout hasToc={hasToc} contentKey={slug}>
<div className="space-y-8"> <div className="space-y-8">
<SectionDivider> <SectionDivider>
<ScrollReveal> <ScrollReveal>

View File

@@ -12,7 +12,7 @@ function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); return twMerge(clsx(inputs));
} }
export function PostLayout({ children, hasToc = true }: { children: React.ReactNode; hasToc?: boolean }) { export function PostLayout({ children, hasToc = true, contentKey }: { children: React.ReactNode; hasToc?: boolean; contentKey?: string }) {
const [isTocOpen, setIsTocOpen] = useState(hasToc); const [isTocOpen, setIsTocOpen] = useState(hasToc);
return ( return (
@@ -43,7 +43,7 @@ export function PostLayout({ children, hasToc = true }: { children: React.ReactN
transition={{ duration: 0.3 }} transition={{ duration: 0.3 }}
className="h-full overflow-y-auto pr-2" className="h-full overflow-y-auto pr-2"
> >
<PostToc /> <PostToc key={contentKey} />
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>
@@ -62,7 +62,7 @@ export function PostLayout({ children, hasToc = true }: { children: React.ReactN
className="fixed bottom-24 right-4 z-40 w-72 rounded-2xl border border-white/20 bg-white/90 p-6 shadow-2xl backdrop-blur-xl dark:border-white/10 dark:bg-slate-900/90 lg:hidden" className="fixed bottom-24 right-4 z-40 w-72 rounded-2xl border border-white/20 bg-white/90 p-6 shadow-2xl backdrop-blur-xl dark:border-white/10 dark:bg-slate-900/90 lg:hidden"
> >
<div className="max-h-[60vh] overflow-y-auto"> <div className="max-h-[60vh] overflow-y-auto">
<PostToc onLinkClick={() => setIsTocOpen(false)} /> <PostToc key={contentKey} onLinkClick={() => setIsTocOpen(false)} />
</div> </div>
</motion.div> </motion.div>
)} )}