DEV Community

Arkadiusz Pawlak
Arkadiusz Pawlak

Posted on

Next.js shallow search params routing

If you ever used Pages Router from Next.js you might be familiar with an ability to do shallow navigation, in App Router this option is gone from router hooks. Currently when you change anything in URL it goes to the Next.js server everytime, it might be unnecessary in cases for example when you have some client only components that does not render on server, but you still want to persist the state in URL (and you probably should do pretty much always, it enhances UX), to prevent that you have to find a magic trick...

And the trick is... just use plain old browser native History API, easier said than done but let's see...

// root page.tsx
import { Suspense } from 'react';
import { ShallowParams } from './shallow-params';

// simple counter so you will clearly see that another
// roundtrip to server occured
let serverRenderCount = 0;

export default async function Home() {
  console.log('Server run', serverRenderCount++);

  return (
    <div className="flex flex-col">
      {/* Suspense is needed because we are using useSearchParams hook which would fallback Next.js to dynamic rendering always */}
      <Suspense>
        <ShallowParams />
      </Suspense>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode
// shallow-params.tsx

'use client';

import { useRouter, useSearchParams } from 'next/navigation';

export function ShallowParams() {
  // useSearchParams updates state even if we use History API
  const params = useSearchParams();
  const router = useRouter();

  const count = +(params.get('count') || 0);
  return (
    <div className="flex flex-col gap-2">
      <p>{params.get('count') || 0}</p>

      <button
        className="btn"
        onClick={() => {
          // shallow increase count params via the push. Check the browser back and forward buttons.
          window.history.pushState({}, '', `/?count=${count + 1}`);
        }}
      >
        Add to count +1 (push)
      </button>
      <button
        className="btn"
        onClick={() => {
          // shallow increase count params via the replace. Check the browser back and forward buttons, the history was overridden
          window.history.replaceState({}, '', `/?count=${count + 5}`);
        }}
      >
        Add to count +5 (replace)
      </button>
      <button
        className="btn"
        onClick={() => {
          // and this is a normal router action, in the server console you will see the serverRenderCount increase and log, with above methods it won't log anything.
          router.push(`/?count=${count + 10}`);
        }}
      >
        Add to count +10 (Next.js router push)
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

And that's pretty much it. Here is a link to StackBlitz sandbox you can play with the example from above

You can build abstractions around it and do whatever you want with shallow routing. For simple things it would be okay to use History API directly, but as always web development have many rough edge cases that would pop out at the least expected moment, that said, there is an excellent library out there called nuqs it makes search params first class citizen in Next.js (and other frameworks also!) and makes sure you won't encounter those rough edge cases.

That's it for today. Take a look at my other articles where I share some nice tricks!

Heroku

Built for developers, by developers.

Whether you're building a simple prototype or a business-critical product, Heroku's fully-managed platform gives you the simplest path to delivering apps quickly — using the tools and languages you already love!

Learn More

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay