How to Persist NextJS Query Params using Link Component
Jasser Mark Arioste
Hello! In this tutorial, you'll learn how to persist NextJS query parameters when navigating to another page.
Introduction #
Sometimes we need to persist query params. For example, when navigating between signup and sign-in pages, we need to persist the redirectURL
for a better user experience.
# if a user was redirected to signup page, but the user already has an existing account http://localhost:3000/signup?next=http%3A%2F%2Flocalhost%3A3000%2Fsubscribe # when the user navigates to sign-in page, the `next` query param should persist http://localhost:3000/signin?next=http%3A%2F%2Flocalhost%3A3000%2Fsubscribe
123456
Another example would be when implementing pagination if your query params have other fields such as page_size
, sort_order
, sort_by
, etc.
http://localhost/3000/users?page=1&page_size=10&sort_by=first_name&sort_order=asc # navigating to page 2 should retain all the other params aside from `page` http://localhost/3000/users?page=2&page_size=10&sort_by=first_name&sort_order=asc
1234
This is a very common pattern when developing web applications. Now, how should we implement this?
The Wrong Approach #
The wrong approach would be to build the query params using a string template or something. For example:
// pages/signup.tsx import Link from "next/link"; import { useRouter } from "next/router"; const SignUpPage = () => { const router = useRouter(); const next = router.query.next as string; return ( <form> <h1>Sign up</h1> <input type="text" placeholder="username" name="username" /> <input type="text" placeholder="password" name="password" /> <button type="submit">Sign up</button> <Link href={`/signin?next=${next}`}>Already have an account</Link> </form> ); }; export default SignUpPage;
123456789101112131415161718
In line 14, we use string interpolation to navigate to the sign-in page while retaining the next
query parameter. However, if there is no next
query parameter, that link will have the following URL: http://localhost:3000/signin?next=undefined
, and therefore you'll have to add more checks or conditions to build this URL. Imagine if you have multiple parameters like the second example above, it's going to be very hard to maintain.
The Solution #
I think the correct approach would be to create a new <Link/>
component that retains the query parameters. First, create the file components/RetainQueryLink.tsx
.
// components/RetainQueryLink.tsx import Link, { LinkProps } from "next/link"; import { useRouter } from "next/router"; import { PropsWithChildren } from "react"; const RetainQueryLink = ({ href, ...props }: LinkProps & PropsWithChildren) => { // 1. use useRouter hook to get access to the current query params const router = useRouter(); // 2. get the pathname const pathname = typeof href === "object" ? href.pathname : href; // 3. get the query from props const query = typeof href === "object" && typeof href.query === "object" ? href.query : {}; return ( <Link {...props} href={{ pathname: pathname, // combine router.query and query props query: { ...router.query, ...query, }, }} /> ); }; export default RetainQueryLink;
123456789101112131415161718192021222324252627282930313233
Now to use this component, we can just do the following:
// pages/signup.tsx import RetainQueryLink from "../components/RetainQueryLink"; const SignUpPage = () => { return ( <form> <h1>Sign up</h1> <input type="text" placeholder="username" name="username" /> <input type="text" placeholder="password" name="password" /> <button type="submit">Sign up</button> <RetainQueryLink href={"/signup"}> Already have an account </RetainQueryLink> </form> ); }; export default SignUpPage;
1234567891011121314151617
For more complex scenarios such as tables, you can still pass query parameters as before. For example:
// pages/users.tsx import RetainQueryLink from "../components/RetainQueryLink"; const UsersPage = () => { const pages = [1, 2, 3, 4, 5]; return ( <div> Users <div> <span>Pagination</span> {pages.map((page) => { return ( <RetainQueryLink key={page} href={{ query: { page }, }} > {` ${page} `} </RetainQueryLink> ); })} </div> <pre>Location:{window.location.href}</pre> </div> ); }; export default UsersPage;
1234567891011121314151617181920212223242526272829
In lines 16-18, we only override the query.page
parameter, all other query parameters, as well as the existing route, are retained.
Not only can you use this with the Link component but when using the router as well. For example:
const router = useRouter(); router.push({ pathname: "/signin", query: router.query, });
123456
Demo #
Here's a simple demo for the second example. Notice that only the page parameter changes.
Full Code #
Check out the full code at my GitHub: jmarioste/next-js-persist-query-params.
Conclusion #
We were able to learn how to persist query parameters using the link component. This is an extremely useful pattern in many tricky scenarios.
If you like this tutorial, please leave a like or share this article. For future tutorials like this, please subscribe to our newsletter or follow me on Twitter.
Credits: Image by Karl Egger from Pixabay