Anchor Navigation in a Next.js Single-Page Application
In Next.js single-page applications, anchor navigation enables seamless transitions between sections without reloading the page. The useAnchorObserver hook from the react-use-observer-hooks library automates scroll and URL synchronization: it detects the visible section during scrolling and scrolls to the anchor when the address changes.
The mechanism works bidirectionally: scrolling updates the URL via IntersectionObserver, while URL changes (from clicks or the back button) trigger scrolling to the section. This prevents feedback loops and ensures proper browser history handling.
Project Setup
Use Next.js 15 with Pages Router, React 19, and Tailwind CSS v4. Install the dependency:
npm install react-use-observer-hooks
Disable built-in scroll restoration in next.config.ts to avoid conflicts with the hook:
const nextConfig = {
experimental: {
scrollRestoration: false,
},
};
Create a catch-all route [[...slug]].tsx to handle paths like /, /about, /contact on a single page.
Main Page Code
Define an array of sections with href, label, and components:
import { useAnchorObserver } from 'react-use-observer-hooks';
import { useRouter } from 'next/router';
const SECTIONS = [
{ href: '/', label: 'Home', Component: HomeSection },
{ href: '/about', label: 'About', Component: AboutSection },
{ href: '/contact', label: 'Join Us', Component: ContactSection },
];
export default function Page() {
const router = useRouter();
const { ref, focusedAnchor } = useAnchorObserver<HTMLDivElement>({
anchors: SECTIONS.map(s => s.href),
currentAnchor: router.asPath,
onAnchorChange: (anchor) => {
router.push(anchor, undefined, { scroll: false, shallow: true });
},
});
return (
<>
<Navbar sections={SECTIONS} activeHref={focusedAnchor} />
<div ref={ref}>
{SECTIONS.map(({ href, Component }) => (
<section key={href} style={{ minHeight: '100vh' }}>
<Component />
</section>
))}
</div>
</>
);
}
Key aspects:
refis applied to the sections container. The hook maps direct children by index to theanchorsarray.scroll: falseinrouter.pushprevents Next.js auto-scrolling.shallow: trueoptimizes navigation without server re-renders.
Navigation Component
Navbar uses <Link> with the same options:
import Link from 'next/link';
export const Navbar = ({ sections, activeHref }) => (
<nav>
{sections.map(({ href, label }) => (
<Link
key={href}
href={href}
scroll={false}
shallow
className={activeHref === href ? 'active' : ''}
>
{label}
</Link>
))}
</nav>
);
focusedAnchor automatically highlights the active item based on the visible section.
Application Behavior
The resulting implementation provides:
- Direct link access to
/aboutlanding in the correct section. - Proper browser back button functionality.
- Search engine indexing with unique URLs.
- Menu synchronization with visible content.
Key Points
- The
useAnchorObserverhook fully automates bidirectional scroll and URL synchronization. - Disabling
scrollRestorationis essential to avoid conflicts. - Catch-all routes in Pages Router are suitable for SPA-like navigation.
shallow: true+scroll: falseminimize re-renders.- Browser history and SEO support come out of the box.
— Editorial Team
No comments yet.