Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jump link anchors dont work #11109

Closed
renatomserra opened this issue Mar 16, 2020 · 38 comments · Fixed by #36430
Closed

Jump link anchors dont work #11109

renatomserra opened this issue Mar 16, 2020 · 38 comments · Fixed by #36430
Labels
good first issue Easy to fix issues, good for newcomers Navigation Related to Next.js linking (e.g., <Link>) and navigation.
Milestone

Comments

@renatomserra
Copy link

renatomserra commented Mar 16, 2020

Bug report

Describe the bug

When clicking an anchor tag in a different page with a jump to id tag, it raises an error and doesnt go.

To Reproduce

Steps to reproduce the behavior, please provide code snippets or a repository:

<a className='nav-link' href='https://github.com/zeit/next.js/#documentation' > Docs </a>

Expected behavior

should redirect to github and scroll to documentation tag

Screenshots

Screenshot 2020-03-16 at 15 13 05

System information

  • Browser (if applies): Chrome
  • Nextjs version: 9.1.6

Additional context

I've tried this in another project without nextjs and it works fine i also tried in a random
place like

https://jsfiddle.net/uqxck4jv/

@djnorrisdev
Copy link

I get that it finds the documentation section with your link (maybe), but will changing the href to this will resolve the error? https://github.com/zeit/next.js/#user-content-documentation

@renatomserra
Copy link
Author

renatomserra commented Mar 16, 2020

same error with that or anything that has #some_element_id to jump to, if i remove it, it works fine to just go to https://github.com/zeit/next.js for example

@painedpineapple
Copy link

I believe I'm also experiencing this issue. Although I am not getting the error, the jump link to a different page doesn't work. It goes to that page but not the 'jump' section.

@rs-8
Copy link

rs-8 commented Apr 23, 2020

@thislogancall
have same thing

@djnorrisdev
Copy link

same error with that or anything that has #some_element_id to jump to, if i remove it, it works fine to just go to https://github.com/zeit/next.js for example

I thought I had it resolved, but in production I'm having the same issue.

@kmturley
Copy link
Contributor

kmturley commented Jul 29, 2020

I'm trying to use anchor tags inside markdown, it works on this page here:
https://nextjs.org/docs/api-reference/cli#build

Seems like there is a lot of custom code to support anchor tags in their docs:
https://github.com/vercel/docs/search?q=permalink&unscoped_q=permalink

Markdown natively supports anchor tags though using:
## [Heading](#anchor-tag)

This will create the tags for you.

But then you have to add the id="anchor-tag" dynamically to the tag using a regex:

const content = this.state.doc.content.replace(/<h2>(.*?)<\/h2>/g, (tag, title) => `<span id="${this.convertToSlug(title)}"></span>${tag}`)
return (
<Layout>
  <Container docs={this.state.allDocs}>
    <h1>{this.state.doc.title}</h1>
    <div
      className={markdownStyles['markdown']}
      dangerouslySetInnerHTML={{ __html: content }}
    />
  </Container>
</Layout>
)

@Seybo
Copy link

Seybo commented Aug 9, 2020

Same issue. Page doesn't scroll to jump link if come from another page. Jump links only work within the same page

@landsman
Copy link
Contributor

Same problem

@Seybo
Copy link

Seybo commented Oct 31, 2020

Any updates here? In the light of NextJs 10 release.
Maybe somebody can post a clear workaround here for the time being?
I've tried several approaches found on SO but couldn't make it work.

@talaikis
Copy link

My ultra-nonsensical solution for this problem, as I needed it asap:

useEffect(() => {
    const path = window.location.hash
    if (path && path.includes('#')) {
      const id = path.replace('#', '')
      const el = window.document.getElementById(id)
      const r = el.getBoundingClientRect()
      window.scrollTo({
        top: r.top,
        behavior: 'smooth'
      })
    }
  })

@Seybo
Copy link

Seybo commented Dec 27, 2020

Here is how i adapted the above because it doesn't work on mobile for me:

  React.useEffect(() => {
    const path = window.location.hash
    if (path && path.includes("#")) {
      setTimeout(() => {
        const id = path.replace("#", "")
        const el = window.document.getElementById(id)
        const r = el.getBoundingClientRect()
        window.top.scroll({
          top: pageYOffset + r.top,
          behavior: "smooth",
        })
      }, 600)
    }

@Timer Timer added good first issue Easy to fix issues, good for newcomers kind: bug labels Dec 28, 2020
@Timer Timer added this to the backlog milestone Dec 28, 2020
@yuvilio
Copy link

yuvilio commented May 19, 2021

We were still seeing this no scrolling to anchor issue in next 10 (on initial page load. Anchors scroll fine if you're already navigating from other pages). For some reason the getBoundingClientRect wasn't correctly calculating the scroll position.. We used scrollIntoView. Can add a setTimeout, per another example above if needed.

  useEffect(() => {
    const path = window.location.hash;
    if (path && path.includes('#')) {
      const id = path.replace('#', '');
      if (id) {
        document
          .querySelector('#' + id)
          .scrollIntoView({ behavior: 'smooth' });
      }
    }
  });

@WiseClock
Copy link

Next.js ^10 fixed it for ASCII anchors, I'm still having problems with CJK characters.

@yahya-aghdam
Copy link

Next.js ^10 fixed it for ASCII anchors, I'm still having problems with CJK characters.

I have no idea about CJK chars but I think you can use Unicode instead of direct CJK

this link may be useful:
https://www.key-shortcut.com/en/writing-systems/%E6%96%87%E5%AD%97-chinese-cjk/cjk-characters-1

@goodroot
Copy link

goodroot commented Jul 19, 2021

This issue is still present in Next.js 11.0.1.

@MincePie
Copy link

has anyone figured out a solution to this. I can't get anchor tags working with next.js at all (same page content).

@michaeljblum
Copy link

Still having this issue. Anchor tag links work when I'm already on the same page as the destination content, but not when I'm on different pages.

Are there any plans to support this functionality or should we just go forward with the less than ideal imperative scroll logic in useEffect?

@JohnTelford
Copy link

I have this issue in Next.js 11.1.1. also

@papertokyo
Copy link

Same here. The links navigate to the page and the hash is preserved, but it doesn't jump to the linked section. If you refresh, the hash remains but the rest of the path disappears so you end up on the home page with the hash appended.

@jasdeepsingh
Copy link

Have this exact same issue.

Why is this not a priority for the Nextjs team? This is breaking a pretty common web pattern for any sites built on top of Nextjs.

@xiaolin
Copy link

xiaolin commented Oct 13, 2021

Came across this and using ref to scroll into view.

e.g. when navigating to /examplepage/#mySection or /examplepage/#myOtherSection

const ExamplePage = () => {
  const router = useRouter();
  const mySection = useRef(null);
  const myOtherSection = useRef(null);

  useEffect(() => {
    const path = router.asPath;
    if (path && path.includes('/#')) {
      const sectionName = path.replace('/#', '');
      switch (sectionName) {
        case 'mySection':
          mySection.current.scrollIntoView({ behavior: 'smooth' });
          break;
        case 'myOtherSection':
          myOtherSection.current.scrollIntoView({ behavior: 'smooth' });
          break;
        default:
          break;
      }
    }
  }, [router.asPath]);

  return (
    <div>
      <section ref={mySection}>
         My section
      </section>
      <section ref={myOtherSection}>
         My section
      </section>
    </div>
  )
}

@M4TH76
Copy link

M4TH76 commented Oct 18, 2021

May a suggest:

useEffect(() => {
	setTimeout(()=>{
		router.replace(window.location.href)
	}, 50)
}, [window.location.hash]) // Fires every time hash changes

@elbotho
Copy link

elbotho commented Oct 22, 2021

Maybe it's useful for somebody:
Still far from a sensible fix, but this code saved me from calculating object positions etc.

It's using the native browser function by just reseting the hash.

  useEffect(() => {
    const hash = window.location.hash
    window.location.hash = ''
    window.location.hash = hash
  })

@nicollecastrog
Copy link

Experiencing this issue in NextJS 11 too. I'm linking from "/page-a" to "/page-b#foo". I think my issue is made worse by the fact that the #foo section on Page B is below the fold, so there's some conflict going on with lazy loading.

A combination of the techniques above is what I ended up going with as a work-around for now:

useEffect(() => {
  const hash = window.location.hash
  if (hash) {
    setTimeout(()=> {
      document
        .querySelector(hash)
        .scrollIntoView({ behavior: "smooth" })
    }, 100)
  }
})

@yahya-aghdam
Copy link

Just use Link component instead of a tag =)

@timneutkens timneutkens added the Navigation Related to Next.js linking (e.g., <Link>) and navigation. label Nov 18, 2021
@dmudro
Copy link

dmudro commented Jan 10, 2022

I am surprised to see this issue which breaks essential scroll to fragment spec.

The above fixes are all based client side JS / DOM telling the user agent to scroll into the fragment via either re-setting the window.location.hash or calling document.scrollIntoView, wrapped in useEffect. If you want to work this globally on all pages, you'd need to add this to _app.js or above _document.js.

The issue is, in the context of Next.js, the DOM is strangely not fully ready at the hook execution, i. e. document.querySelector('#fragment') might return null. I suspect this is why folks started wrapping the logic inside a setTimeout; using a timer to get around such issue is almost always a code smell in first place.

This will require Next.js core fix I am afraid.

@dmudro
Copy link

dmudro commented Jan 10, 2022

Been trying to fix this by subscribing to a on dom ready event in _document.tsx, which unsurpsigingly is not work inside a React app:

// following does not work
<script
  dangerouslySetInnerHTML={{
    __html: `document.addEventListener('DOMContentLoaded', () => { const hash = window.location.hash; console.log('DOMContentLoaded hash', hash); const el = hash && document.querySelector(hash); console.log('DOMContentLoaded el', el); el && el.scrollIntoView({ behavior: 'smooth' }); });`
  }}
/>

On window load would work, although the UX might be undesirable as the user could already interacted with the page bofore all assets are loaded:

<script
  dangerouslySetInnerHTML={{
  __html: `window.addEventListener('load', () => { const hash = window.location.hash; console.log('window.load hash', hash); const el = hash && document.querySelector(hash); console.log('window.load el', el); el && el.scrollIntoView({ behavior: 'smooth' }); });`
}}
/>

...hopefully that will give someone a good idea how to come up with a robust fix.

In theory it could work by subscribing to one of Router events and do the fragment scrolling business there but I am unable to get the router events working in my app right now.

@TheGodOfAwesome
Copy link

TheGodOfAwesome commented Jan 11, 2022

Found a simple HTML work around no js required
Wrap the components you want to jump to in basic divs with the id so that nextjs can SSR them and then add the link to the div as usual

export default function Home() {

  return (
    <div>
      <Head>
        <title>Example</title>
        <meta name="description" content="example" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <div id="waitlist">
        <ImageInfoCard/>
      </div>
      <div id="explore">
        <Explorer/>
      </div>
    </div>
  )
}

<Link href="#waitlist">Subscribe</Link>

@TheoOliveira
Copy link

Just install https://www.npmjs.com/package/react-scrollchor and worked! Have a nice day of coding guys!

@ibraheemosule
Copy link

Been trying to fix this by subscribing to a on dom ready event in _document.tsx, which unsurpsigingly is not work inside a React app:

// following does not work
<script
  dangerouslySetInnerHTML={{
    __html: `document.addEventListener('DOMContentLoaded', () => { const hash = window.location.hash; console.log('DOMContentLoaded hash', hash); const el = hash && document.querySelector(hash); console.log('DOMContentLoaded el', el); el && el.scrollIntoView({ behavior: 'smooth' }); });`
  }}
/>

On window load would work, although the UX might be undesirable as the user could already interacted with the page bofore all assets are loaded:

<script
  dangerouslySetInnerHTML={{
  __html: `window.addEventListener('load', () => { const hash = window.location.hash; console.log('window.load hash', hash); const el = hash && document.querySelector(hash); console.log('window.load el', el); el && el.scrollIntoView({ behavior: 'smooth' }); });`
}}
/>

...hopefully that will give someone a good idea how to come up with a robust fix.

In theory it could work by subscribing to one of Router events and do the fragment scrolling business there but I am unable to get the router events working in my app right now.

Where is the script tag meant to be placed? is it in the page component head tag?

@dmudro
Copy link

dmudro commented Mar 14, 2022

in regard to my earlier comments and my particular case - the culprit was custom auth logic which delayed DOM rendering on some pages which in turn prevented scroll-to-anchor behaviour on SSR pages. hence it was a very custom source of app bug in my case.

to be clear I never experienced the explicit js error described in the original issue above. I trust that original bug was fixed and provided that assumption is correct, this GH issue should be fixed to prevent more confusion @Timer

I trust other suggested solutions mainly around using setTimeout or employing other 3rd party packages for a simple scroll fix are not optimal. perhaps these suggestions are trying to fix some other issue with scrolling / anchors or their bugs are rooted in the custom app as it was in my case.

finally, previously mentioned events document.addEventListener('DOMContentLoaded', () => {... and window.addEventListener('load', () => {... do work perfectly well in _document.tsx; you may struggle with Script by next/script because of afterInteractive strategy. once again a fix based on either DOM event was not necessary in my case at the end of the day.

@ibraheemosule
Copy link

in regard to my earlier comments and my particular case - the culprit was custom auth logic which delayed DOM rendering on some pages which in turn prevented scroll-to-anchor behaviour on SSR pages. hence it was a very custom source of app bug in my case.

to be clear I never experienced the explicit js error described in the original issue above. I trust that original bug was fixed and provided that assumption is correct, this GH issue should be fixed to prevent more confusion @Timer

I trust other suggested solutions mainly around using setTimeout or employing other 3rd party packages for a simple scroll fix are not optimal. perhaps these suggestions are trying to fix some other issue with scrolling / anchors or their bugs are rooted in the custom app as it was in my case.

finally, previously mentioned events document.addEventListener('DOMContentLoaded', () => {... and window.addEventListener('load', () => {... do work perfectly well in _document.tsx; you may struggle with Script by next/script because of afterInteractive strategy. once again a fix based on either DOM event was not necessary in my case at the end of the day.

This was super helpful... thanks a lot

@robksawyer
Copy link

robksawyer commented Apr 11, 2022

I ended up integrating https://www.npmjs.com/package/react-scroll and that worked pretty well. It'd be nice to use a simple Link tag though.

import { scroller } from 'react-scroll';
...

export default function MyComponent() {
 React.useLayoutEffect(() => {
    const scrollHash = () => {
      scroller.scrollTo(section, {
        duration: 1000,
        delay: 0,
        smooth: true,
      });
    };
    if (section) {
      setTimeout(scrollHash, 700);
    }
  }, []);
...
}

@GregBorrelly
Copy link

GregBorrelly commented Apr 21, 2022

Seeing the same issue on Next v12.0.9 - anchors work when you click a link within the same page, but if you refresh or navigate to another page by clicking <Link href={"/secondPage#subsection"} > you end up at the top of page.

@dmudro
Copy link

dmudro commented Apr 21, 2022

Seeing the same issue on Next v12.0.9 - anchors work when you click a link within the same page, but if you refresh or navigate to another page by clicking <Link href={"/secondPage#subsection"} > you end up at the top of page.

make sure that the #subsection element is not behind some async logic or anything that would cause that element not be in the DOM yet i. e. at render time in the full refresh scenario

@kodiakhq kodiakhq bot closed this as completed in #36430 May 22, 2022
kodiakhq bot pushed a commit that referenced this issue May 22, 2022
fixes #11109 

## Bug

- [x] Related issues linked using `fixes #number`
- [x] Integration tests added
- [ ] Errors have helpful link attached, see `contributing.md`

## Feature

- [ ] Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
- [ ] Related issues linked using `fixes #number`
- [ ] Integration tests added
- [ ] Documentation added
- [ ] Telemetry added. In case of a feature if it's used or not.
- [ ] Errors have helpful link attached, see `contributing.md`

## Documentation / Examples

- [ ] Make sure the linting passes by running `yarn lint`
@remcode-remco
Copy link

remcode-remco commented May 22, 2022

Maybe it's useful for somebody: Still far from a sensible fix, but this code saved me from calculating object positions etc.
It's using the native browser function by just reseting the hash.

  useEffect(() => {
    const hash = window.location.hash
    window.location.hash = ''
    window.location.hash = hash
  })

This is a quick uncomplicated fix, thanks!

PS Changed to this, otherwise a '#' is added to every url:

  useEffect(() => {
    const hash = window.location.hash
   if (hash.length>0) {
     window.location.hash = ''
     window.location.hash = hash
   }
  })

@jmayergit
Copy link

This was super helpful... thanks a lot

Yeah I found @dmudro 's answer helpful as well. As he says setTimeout is not optimal but I think we all know this. Importantly he encourages us to find the potential problem causing our errors

@github-actions
Copy link
Contributor

This closed issue has been automatically locked because it had no new activity for a month. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 15, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
good first issue Easy to fix issues, good for newcomers Navigation Related to Next.js linking (e.g., <Link>) and navigation.
Projects
None yet
Development

Successfully merging a pull request may close this issue.