Back to Home

Anchor navigation in Next.js with useAnchorObserver

The article describes the implementation of anchor navigation in a single-page Next.js application using the useAnchorObserver hook. Scroll and URL synchronization is automated, browser history and SEO support. Full code with settings is provided.

Single-page app on Next.js: anchor navigation without bugs
Advertisement 728x90

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:

Google AdInline article slot
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:

Google AdInline article slot
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:

  • ref is applied to the sections container. The hook maps direct children by index to the anchors array.
  • scroll: false in router.push prevents Next.js auto-scrolling.
  • shallow: true optimizes 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.

Google AdInline article slot

Application Behavior

The resulting implementation provides:

  • Direct link access to /about landing in the correct section.
  • Proper browser back button functionality.
  • Search engine indexing with unique URLs.
  • Menu synchronization with visible content.

Key Points

  • The useAnchorObserver hook fully automates bidirectional scroll and URL synchronization.
  • Disabling scrollRestoration is essential to avoid conflicts.
  • Catch-all routes in Pages Router are suitable for SPA-like navigation.
  • shallow: true + scroll: false minimize re-renders.
  • Browser history and SEO support come out of the box.

— Editorial Team

Advertisement 728x90

Read Next