61

In my NextJS app, I have a language selector that's visible on every page. When I select a new language, I just want to replace the current URL by appending a query param lang=en to it.

Here's the function that replaces the URL:

const changeLanguage = (lang: LanguageID) => {
    replace({
      pathname,
      query: { ...query, lang },
    });
  };

In this example, replace, query and pathname are coming from the next router.

Now, everything works for static routes, but I'm unable to make it work for dynamic routes. For example, I have the following folder structure:

pages
|_customers
|__index.tsx
|__[customerId].tsx

If I'm on http://localhost/customers and I change my language to English, the URL changes to http://localhost/customers?lang=en which is what I want. However, if I'm on http://localhost/customer/1 and I change my language to English, the URL changes to http://localhost/customers/[customerId]?customerId=1&lang=en, instead of the URL I'm expecting http://localhost/customers/1?lang=en.

Now, I know that I could use asPath on the router, and reconstruct the query string object by appending lang to it, but I feel that it's something that should be build into Next. Also, I know it could be easily done with vanilla JS, but it's not the point here.

Am I missing something? Is there an easier way to append query params to a dynamic route without doing a server-side re-rendering?

Thanks

6
  • Instead of using next.js route api, use window.location. Commented Apr 27, 2020 at 21:46
  • Question in changeLanguage = (lang: LanguageID) are you trying to set a default value? Or is this TypeScript syntax? Commented Apr 27, 2020 at 21:52
  • @JoseFelix like I said in my post, I know it can easily be done in vanilla JS, but I want to avoid that if possible. Commented Apr 27, 2020 at 21:57
  • I think the easiest way here is to give the pathname property the asPath value from the router. Commented Apr 27, 2020 at 22:18
  • @JoseFelix the problem with that solution is that it causes a server-side re-rendering, which I want to avoid if possible. That's why I asked the question, but I'm starting to feel like it's not really possible with Next... Commented Apr 27, 2020 at 23:51

13 Answers 13

72

Just add more param to current router then push itself

const router = useRouter();
router.query.NEWPARAMS = "VALUE"
router.push(router)
Sign up to request clarification or add additional context in comments.

5 Comments

Just take caution if you use rewrites, because this approach won't work. This will change the browser url to the path the rewrite points to inside the pages folder, instead of using the correct url. Seems like a nextjs bug to me.
I think this approach is outdated
This won't work properly if server.js is used for rendering pages where actual path and rendered path may not be same.
Definitely causes problems - including Unknown key passed via urlObject into url.format errors.
Even though there may be some contextual issues with this approach, the logic above at least worked for the context I needed and also works with router.replace (probably with the same caveats as router.push).
38

The solution which doesn't need to send the whole previous route, as replace just replaces what we need to replace, so query params:

const router = useRouter();
router.replace({
   query: { ...router.query, key: value },
});

2 Comments

It doesn't only replace query params, but: replace will prevent adding a new URL entry into the history stack.
This solved it for me! Using the selected answer gave me a Unknown key passed via urlObject into url.format error
22

If we want to have this as a link - use it like so:

const router = useRouter();


<Link
    href={{
        pathname: router.pathname,
        query: { ...router.query, lang },
    }}
    passHref
    shallow
    replace
></Link>

3 Comments

Worked for me best :)
This works but encode special chars from my current url, eg switch from localhost/maps/@55.64817,-20.98599,13z to encoded "@" & ","
Wow for once Next.js made something easy!!!
10

I tried adding my param to the route query and pushing the router itself, as mentioned here, it works, but I got a lot of warnings: enter image description here

So, I then pushed to / and passed my query params as following:

const router = useRouter();
router.push({ href: '/', query: { myQueryParam: value  } });

I hope that works for you too.

1 Comment

This works for me with Next.JS 13. To update a single param when you have multiple, spread the current params into the push invocation: router.push({ href: "./", query: { ...router.query, myQueryParam: value } })
10

An alternative approach when you have dynamic routing in Next.js, and want to do a shallow adjustment of the route to reflect updated query params, is to try:

const router = useRouter()
const url = {
      pathname: router.pathname,
      query: { ...router.query, page: 2 }
    }
router.push(url, undefined, { shallow: true })

This will retreive the current path (router.pathname) and query (router.query) details, and merge them in along with your new page query param. If your forget to merge in the existing query params you might see an error like:

The provided href value is missing query values to be interpolated properly

1 Comment

Now in next 13 you'll get pathname/query does not exist on type AppRouterInstance. It is because the 'query' object has been removed from useRouter hook. So right now, you have to use 'useSearchParams()'. Check out new documentation - nextjs.org/docs/app/api-reference/functions/use-search-params
7

for nextjs 13 I just found this,

import { useRouter } from 'next/navigation';
router.push(`?page=${page}`)

you just need to handle every query you want

1 Comment

Update: if you are using <= 14, it's good to use nuqs. You can check this link; I hope that helps you
3

If you are using App Router, The query object has been removed and is replaced by useSearchParams(), you need to update searchParams:

export default function ExampleClientComponent() {
  const router = useRouter()
  const pathname = usePathname()
  const searchParams = useSearchParams()
 
  // Get a new searchParams string by merging the current
  // searchParams with a provided key/value pair
  const createQueryString = useCallback(
    (name: string, value: string) => {
      const params = new URLSearchParams(searchParams.toString())
      params.set(name, value)
 
      return params.toString()
    },
    [searchParams]
  )
 
  return (
    <>
      {/* using useRouter */}
      <button
        onClick={() => {
          // <pathname>?sort=asc
          router.push(pathname + '?' + createQueryString('sort', 'asc'))
        }}
      >
        ASC
      </button>
 
      {/* using <Link> */}
      <Link
        href={
          // <pathname>?sort=desc
          pathname + '?' + createQueryString('sort', 'desc')
        }
      >
        DESC
      </Link>
    </>
  )
} 

Comments

2

I ended up using the solution that I wanted to avoid in the first place, which was to play with the asPath value. Atleast, there's no server-side re-rendering being done since the path is the same.

Here's my updated changeLanguage function (stringifyUrl is coming from the query-string package)

  const changeLanguage = (lang: LanguageID) => {
    const newPathname = stringifyUrl({ url: pathname, query: { ...query, lang } });
    const newAsPath = stringifyUrl({ url: asPath, query: { lang } });
    replace(newPathname, newAsPath);
  };

Comments

2

If anyone is still looking the answer ,for Next,js ^11.1.2.I hope this helps you out.Use

const router = useRouter();
router.push({ pathname: "/search", query: { key: key } });

1 Comment

this way I'll need to manually remount the path
2

In latest version, Next 13, some of the functionality moved to other hooks, which query and path are some of them. You can use useSearchParams to get query and usePathname instead of pathname. By the time I am writing this answer, it does not have a stable version and you can find the beta documents here: https://beta.nextjs.org/docs/api-reference/use-router

Comments

0

The best solution I could come up with is that doesn't cause the Unknown key passed via urlObject into url.format is to do this pattern router.query.plan = plan.title; router.push({ query: router.query });

Comments

0

I see the top answer already resolved this, just wanted to add, if you have for example table filters or page result filters, do not do ‘router.replace()’. Always push to preserve the browser history. This way the user can go back with browser arrows and query params will still work.

Comments

-1
  let queryParams;
  if (typeof window !== "undefined") {  
    // The search property returns the querystring part of a URL, including the question mark (?).
    queryParams = new URLSearchParams(window.location.search);
    // quaeryParams object has nice methods
    // console.log("window.location.search", queryParams);
    // console.log("query get", queryParams.get("location"));
  }

inside changeLanguage,

const changeLanguage = (lang: LanguageID) => {
    if (queryParams.has("lang")) {
      queryParams.set("lang", lang);
    } else {
      // otherwise append
      queryParams.append("lang", lang);
    }
    router.replace({
      search: queryParams.toString(),
    });
  };

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.