#Use URL hash in app router Next.js 15

1 messages · Page 1 of 1 (latest)

past bay
#

I am currently using tis to listen to changes to url hash but it is not detecting changes even tho the url is changing.

import { useCallback, useEffect, useLayoutEffect, useState } from "react";

type UseHashReturn = [string, (newHash: string) => void];

const useIsomorphicLayoutEffect =
typeof window !== "undefined" ? useLayoutEffect : useEffect;

/**

  • Hook to read and write the hash of the current url. It also updates the hash when the url changes.
  • @returns {UseHashReturn} An array of two elements:
    1. The current hash of the url.
    1. A function to set the hash of the url.
      */
      export function useHash(): UseHashReturn {
      const [hash, setHash] = useState(() =>
      typeof window !== "undefined" ? window.location.hash : "",
      );

const onHashChange = useCallback(() => {
setHash(window.location.hash);
}, []);

useIsomorphicLayoutEffect(() => {
window.addEventListener("hashchange", onHashChange);
return () => {
window.removeEventListener("hashchange", onHashChange);
};
}, []);

const _setHash = useCallback(
(newHash: string) => {
if (newHash !== hash) {
window.location.hash = newHash;
}
},
[hash],
);

return [hash, _setHash] as const;
}

grave heraldBOT
#

Hi @past bay. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

Based on the knowledge sources provided, it appears that the approach you're using to listen for hash changes in Next.js 15 with the App Router is not the recommended method. In Next.js 15, there are specific APIs and patterns for handling URL changes, including hash changes. Let me explain the recommended approach:

  1. Instead of using the native window.addEventListener("hashchange", ...), Next.js provides client-side hooks for observing URL changes, including hash changes.

  2. The recommended way to listen for URL changes, including hash changes, is to use a combination of usePathname and useSearchParams hooks from Next.js.

Here's an example of how you can implement this in Next.js 15 with the App Router:

'use client'

import { useEffect } from 'react'
import { usePathname, useSearchParams } from 'next/navigation'

export function NavigationEvents() {
  const pathname = usePathname()
  const searchParams = useSearchParams()

  useEffect(() => {
    const url = `${pathname}?${searchParams}`
    console.log(url)
    // You can now use the current URL, including the hash
    // ...
  }, [pathname, searchParams])

  return null
}

You can then use this component in your layout:

import { Suspense } from 'react'
import { NavigationEvents } from './components/navigation-events'

export default function Layout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}

        <Suspense fallback={null}>
          <NavigationEvents />
        </Suspense>
      </body>
    </html>
  )
}

This approach is described in the Next.js documentation on Router Events (https://nextjs.org/docs/app/api-reference/functions/use-router#examples).

#

If you specifically need to update the hash, you can use the native window.history.pushState or window.history.replaceState methods, which integrate with the Next.js Router. Here's an example:

'use client'

import { useSearchParams } from 'next/navigation'

export default function UpdateHash() {
  const searchParams = useSearchParams()

  function updateHash(newHash: string) {
    const params = new URLSearchParams(searchParams.toString())
    params.set('hash', newHash)
    window.history.pushState(null, '', `#${params.toString()}`)
  }

  return (
    <button onClick={() => updateHash('newHash')}>Update Hash</button>
  )
}

This approach is described in the Next.js documentation on Using the native History API (https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating#using-the-native-history-api).

By using these Next.js-specific APIs and patterns, you should be able to reliably detect and update hash changes in your Next.js 15 application with the App Router.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@dark schooner Kapa.ai is still learning and improving, please let me know how I did by reacting below.

past bay
#

@grave herald I am not able to access hash value changes using any of the ablove

grave heraldBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

I apologize for the confusion in my previous response. After carefully reviewing the knowledge sources again, I can see that there is indeed an issue with detecting hash changes in Next.js 15 using the App Router. Let me explain the current situation based on the available information:

  1. The App Router in Next.js 15 seems to have a problem with detecting hash changes. This issue has been reported and discussed in a GitHub issue Nested sitemaps seem to 404 in Next.js 15 (https://github.com/vercel/next.js/issues/72787).

  2. The usePathname() and useSearchParams() hooks do not capture hash changes. This limitation is mentioned in another GitHub issue Server rendered routes with hashes do not work with browser back/forward navigation (https://github.com/vercel/next.js/issues/56112).

  3. There's no built-in solution in Next.js 15 that reliably detects hash changes in the App Router.

Given this information, you're correct that the methods I suggested earlier are not working as expected for detecting hash changes.

As a workaround, one user suggested implementing a custom solution:

"use client"

import { useState, useEffect, useCallback } from 'react';
import { useRouter, usePathname } from 'next/navigation';

export default function FollowHashRoute() {
  const [latestPath, setLatestPath] = useState('');
  const pathname = usePathname();
  const router = useRouter();

  const onPopState = useCallback((e: PopStateEvent) => {
    const newPathname = new URL(document.location.href).pathname;

    if (newPathname !== latestPath) {
      setLatestPath(newPathname);
      router.replace(document.location.href);
    }
  }, [router, latestPath]);

  useEffect(() => {
    window.addEventListener('popstate', onPopState);

    return () => {
      window.removeEventListener('popstate', onPopState);
    };
  }, [onPopState]);
#

if (pathname !== latestPath) {
setLatestPath(pathname);
}

return null;
}


This component can be added to your global layout to force a `router.replace` when detecting the pathname didn't follow URL changes, including hash changes.

However, it's important to note that this is a workaround and not an official solution. The Next.js team is aware of this issue, and it may be addressed in future updates.

For now, if you need reliable hash change detection, you might need to consider using the Pages Router instead of the App Router, or implement a custom solution like the one above.

**Next.js GitHub Issue #56112** (<https://github.com/vercel/next.js/issues/56112>)
**Next.js GitHub Issue #72787** (<https://github.com/vercel/next.js/issues/72787>)
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
#

@dark schooner Kapa.ai is still learning and improving, please let me know how I did by reacting below.

past bay
#

@grave herald write me useEffect code that would react to changes in a state and scroll to the id in the state

grave heraldBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the information provided in the knowledge sources, I can offer a solution that uses useEffect to react to changes in a state and scroll to an element with a specific ID. Here's an example of how you can implement this in Next.js:

'use client'

import { useEffect } from 'react';

export default function ScrollToElement({ elementId }: { elementId: string }) {
  useEffect(() => {
    if (elementId) {
      const element = document.getElementById(elementId);
      if (element) {
        element.scrollIntoView({ behavior: 'smooth' });
      }
    }
  }, [elementId]);

  return null;
}

This component can be used in your page or layout like this:

'use client'

import { useState } from 'react';
import ScrollToElement from './ScrollToElement';

export default function YourComponent() {
  const [activeElementId, setActiveElementId] = useState('');

  // ... other component logic

  return (
    <>
      {/* Your other components */}
      <ScrollToElement elementId={activeElementId} />
    </>
  );
}

In this implementation, whenever the elementId state changes, the useEffect hook will trigger and scroll to the element with the matching ID.

However, it's important to note that this solution might not work perfectly in all scenarios with Next.js App Router, especially if you're dealing with dynamic routes or server-side rendering. The scrolling behavior might be affected by how Next.js handles client-side navigation and component mounting.

If you encounter issues with this approach, you might need to consider using a more robust solution or a third-party library that's compatible with Next.js App Router.

Next.js GitHub Issue #64441 (https://github.com/vercel/next.js/issues/64441)

#

Remember to test this solution thoroughly in your specific use case to ensure it meets your requirements.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: