skip to Main Content

I am using Nextjs 13 with the experimental App Dir but am not sure if this problem I am facing has anything to do with the issue I am facing. I have an id in my home page of "faqs" and when I click on the link, I can see it successfully goes to that link but does nothing in the browser. If I am on another page, I click the link and it takes me to the home page with the correct url but still stays on the top of the page and does not scroll to the indicated id. I did implement scroll={false} as suggested in the documentation but it makes no difference.

Here is a snippet of the relevant code parts:

"use client"
import React, { useState } from "react"
import { useRouter } from "next/navigation"
import Link from "next/link"

const Navigation = () => {
  const router = useRouter()
 ...

In the return:

 <Link scroll={false} href="/#faqs">FAQS</Link>

I Even tried:

<button type="button" onClick={() => router.push("/#faqs")}>FAQS</button>

In React the hash works fairly well but in next js, even only in client rendering it seems convoluted. If anyone knows what I am doing wrong or if there is a viable work around, I would sure appreciate it.
Thank you in advance.
If I am missing anything, please let me know.

2

Answers


  1. I use hashtags a lot and I plan to start using the app directory in future projects, so I dug into this and it’s not pretty. Apparently, NextJS uses a different package for app directory components client-side called "next/navigation". It’s very different from "next/router". Also, when using "next/link" elements, NextJS does not trigger the onRouteChangeComplete event when location.hash changes but location.pathname does not.

    So, in order to detect a hash change and scroll to the associated element, I finally had to implement this hack:

    "use client"
    import { Inter } from '@next/font/google'
    import paragraph from './paragraph'
    import Link from 'next/link'
    import { useEffect, useState } from 'react'
    
    const inter = Inter({ subsets: ['latin'] })
    
    export default function Home() {
      const [navClick, setNavClick] = useState(false);
    
      useEffect(() => {
        setTimeout(() => {
          const hash = window.location.hash;
          if (hash) document.querySelector(hash).scrollIntoView();
        }, 0);
      }, [navClick])
    
      const toggleNavClick = () => setNavClick((oldVal) => !oldVal);
    
      return (
        <main>
          <nav>
            <ul>
              <li>
                <Link href="/#one" onClick={toggleNavClick}>Section One</Link>
              </li>
              <li>
                <Link href="/#two" onClick={toggleNavClick}>Section Two</Link>
              </li>
              <li>
                <Link href="/#three" onClick={toggleNavClick}>Section Three</Link>
              </li>
            </ul>
          </nav>
          <div className="container">
            <section id="one">
              <h1>Section One</h1>
              <div dangerouslySetInnerHTML={{ __html: paragraph }} />
            </section>
            <section id="two">
              <h1>Section Two</h1>
              <div dangerouslySetInnerHTML={{ __html: paragraph }} />
            </section>
            <section id="three">
              <h1>Section Three</h1>
              <div dangerouslySetInnerHTML={{ __html: paragraph }} />
            </section>
          </div>
        </main>
      )
    }
    

    Since the hash change cannot be detected because no event is triggered, I basically created an event by toggling navClick each time a link is clicked. The navigation logic is enclosed in setTimeout() function because it triggers after window.location is updated.

    Repo: https://github.com/designly1/next-hash-test
    Demo: https://next-hash-test.vercel.app/

    Login or Signup to reply.
  2. My workaround is to not use the Link component:

    <a href="#faqs">FAQS</a>
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search