Debugging a Privacy Page Scroll Jump Bug in Next.js

June 30, 2026Web101 by HanWeb Development

Build log on debugging a Next.js Privacy Policy page scroll jump issue by removing hash anchors, disabling smooth scroll on the page, adding a privacy-only scroll stabilizer, clearing stale hash state, and verifying the page with build and local HTML checks.

Debugging a Privacy Page Scroll Jump Bug in Next.js

Problem

The Problem

Implementation

Practical Checklist

Result

Fix Direction

Introduction

I ran into a page-specific frontend bug on a Next.js site: the Privacy Policy page kept jumping up and down after loading.

The confusing part was that the problem only happened on the Privacy page. Other pages were stable. That meant the issue was probably not a global layout problem, not a general browser problem, and not a simple CSS spacing issue.

This note documents how I debugged the Privacy page as its own scroll behavior bug, instead of continuing to guess from common causes.

The Problem

The observed behavior was simple:

Open /privacy

The page loads

The scroll position moves by itself

The page appears to jump down and back up

Only the Privacy page has this problem

This kind of bug is frustrating because it does not always show up as an obvious error in the console. The page renders, the content exists, and the route returns 200. But the reading experience is broken because the browser keeps changing the scroll position.

The Wrong Assumption

The first wrong assumption was treating it like a generic layout issue.

Common guesses include:

Maybe an image loads late

Maybe the consent banner changes height

Maybe the sticky nav shifts the layout

Maybe hydration changes the page height

Maybe an ad placeholder is causing movement

Those can be valid causes on some pages, but they did not explain why only the Privacy page kept jumping.

The better question was:

What does the Privacy page have that other pages do not?

Source Inspection

After inspecting the Privacy page source, the suspicious pattern became clearer.

The page still had many internal hash links and section targets:

href="#overview"

href="#cookies"

href="#contact"

href="#manage-consent"

section id="cookies"

section id="contact"

scroll-mt classes

At the same time, the site had global smooth scrolling enabled.

That combination made the Privacy page different from normal article pages. It had many built-in scroll targets and several ways for the browser to reposition the document.

Root Cause

The likely root cause was not one single line of code. It was the interaction between several scroll behaviors:

Privacy page hash anchors

Section id targets

Global smooth scroll

Browser scroll restoration

Possible stale URL hash

Consent or page hydration timing

A browser can remember a previous scroll position. A hash in the URL can ask the browser to jump to a section. Smooth scrolling can animate that movement. React hydration can then change timing again.

When all of these exist on the same page, the result can look like the page is moving on its own.

Fix Direction

The Privacy page did not actually need interactive hash navigation.

A Privacy Policy page is mostly a reading page. It does not need quick links that jump the user around while the browser is also trying to restore scroll position.

So the fix direction was:

Remove internal hash navigation

Remove section hash targets

Disable smooth scroll only for this page

Prevent browser scroll restoration on /privacy

Force the page to start at the top

Verify actual served HTML, not only source files

This keeps the Privacy page stable without changing scroll behavior across the whole site.

Fix 1: Remove Internal Hash Links

The first fix was to remove clickable internal hash links from the Privacy page.

Instead of rendering quick links like this:

<a href="#cookies">Cookies</a>

<a href="#contact">Contact</a>

<a href="#manage-consent">Manage consent</a>

I changed the quick-link area into a plain topic list.

The list still helps the reader understand the page structure, but it no longer triggers browser-level hash navigation.

Fix 2: Remove Section Hash Targets

Removing links was not enough. If the page still had section ids, a stale hash in the URL could still target those sections.

So I also removed the section-level scroll targets from the Privacy page:

section id="cookies"

section id="contact"

section id="manage-consent"

scroll-mt-24

That made the page behave more like a normal document instead of a page with many addressable scroll targets.

Fix 3: Add a Privacy-Only Scroll Stabilizer

The next fix was to add a page-specific scroll stabilizer.

The stabilizer is only for the Privacy page. Its job is to keep this page from restoring an old scroll position or reacting to a stale hash.

The behavior is:

Set history.scrollRestoration to manual

Clear stale hash state when needed

Scroll to the top on load

Avoid affecting other pages

This is important because a global scroll fix would be too risky. Other pages may still need normal browser scroll restoration.

Fix 4: Disable Smooth Scroll Only When Needed

The site used smooth scrolling globally. Smooth scrolling is usually fine for anchor navigation, but it can make debugging scroll jumps harder because it turns position changes into visible movement.

Instead of removing smooth scrolling globally, I added a page-specific class:

html.no-smooth-scroll

Then the Privacy page can temporarily disable smooth scroll while it stabilizes the initial scroll position.

This keeps the fix narrow. The Privacy page becomes stable without changing the interaction behavior of unrelated pages.

Fix 5: Add an Early Scroll Guard

A React component runs after the page starts hydrating. For a scroll bug, that may be too late because the browser can restore position before React finishes loading.

So I added an early guard directly in the Privacy page output.

The purpose was:

Run before hydration

Disable scroll restoration early

Clear stale hash behavior early

Start from the top before the user sees movement

This is the difference between fixing a jump after it happens and preventing the jump from happening in the first place.

Verification

After the changes, I verified both the source and the actual served page.

The checks were:

No href="#..." remains on /privacy

No section hash target remains on /privacy

No scroll-mt target remains on /privacy

npm run build passed

/privacy returned 200 locally

Served HTML contained the early scroll guard

Served HTML did not contain old hash anchors

This step matters because checking source files alone is not enough. A dev server can serve stale output, and a page can still contain generated HTML that differs from what I expected.

Before and After

Before the fix, the Privacy page had multiple scroll triggers:

Quick links used href="#..."

Sections had matching ids

Global smooth scroll was active

The browser could restore an old scroll position

A stale hash could target a lower section

The page appeared to jump during load

After the fix, the Privacy page became a stable reading page:

Quick links became plain text

Section hash targets were removed

Smooth scroll could be disabled only for this page

Scroll restoration was set to manual

The page starts from the top

Build and local route checks passed

The Main Gotcha

The main gotcha is that scroll bugs can be caused by several small features working together.

A hash link is normal. Smooth scroll is normal. Browser scroll restoration is normal. Sticky navigation is normal. Hydration is normal.

But on a long policy page, all of them together can create unstable behavior.

The practical lesson is:

If only one page scrolls by itself, inspect that page’s anchors, ids, hash state, and scroll restoration behavior before blaming the whole layout.

Mental Model

The mental model I use now is:

href="#section" = browser may jump

section id="section" = valid scroll target

scroll-smooth = movement becomes animated

scroll restoration = browser may return to previous position

hydration = timing can change after initial HTML

long policy page = more room for visible jumps

A stable page should not have unnecessary scroll targets. If the page is mainly for reading, a plain table of contents can be safer than hash navigation.

Practical Checklist

For a page-specific scroll jump bug, I would check:

1. Does the page have href="#..." links?

2. Does the page have matching section ids?

3. Is global smooth scroll enabled?

4. Does the URL contain a stale hash?

5. Is the browser restoring an old scroll position?

6. Does a consent banner or overlay change layout timing?

7. Does the page scroll before or after React hydration?

8. Does the served HTML still contain old anchors?

9. Does the route return 200 after the fix?

10. Does npm run build still pass?

This checklist is more useful than guessing from generic layout bugs.

Final Takeaway

The final takeaway is simple:

A Privacy Policy page should be stable before it is interactive.

For this bug, the best fix was not adding more JavaScript everywhere. It was removing unnecessary hash navigation, disabling smooth scroll only where needed, preventing browser scroll restoration on the Privacy page, and verifying the actual served HTML.

That made the Privacy page behave like a stable reading page instead of a page with competing scroll instructions.

POSTED IN
Next.jsFrontend DebuggingPrivacy PageScroll RestorationSmooth ScrollReactTechnical Notes

Related stories

Curated reads to continue the thread.