BLOG
Next.js 14: The Power of URL in State Management
#d illustration of a woman coding surrounded by holograms

Mauro Davoli

Frontend Engineer

Nov 24, 2023

5 min read

Next.js

Tools

Frontend

In the ever-evolving landscape of web development, a silent hero has emerged — the URL. As frontenders navigate the challenges of integrating a state manager into server components, the URL, provides a welcome solution in Next.js development.

Beyond its traditional role as a web address, the URL has transformed into a powerful state management tool, revolutionizing how users interact with and save their online experiences.

This method shows how the URL can do a bunch of things, giving power to both users and developers to:

  • share their curated content with a simple link.
  • bookmark pages for future reference
  • seamlessly save search results

Let’s apply this concept with a practical example in a server component.


import Link from 'next/link';

export default function ProductPage({
  searchParams,
}: {
  searchParams: { [key: string]: string | string[] | undefined };
}) {
  const selectedColor = (searchParams?.color || 'blue') as string;
  const selectedSize = (searchParams?.size || 'M') as string;

  const sizeVariants = ['S', 'M', 'L', 'XL'];

  return (
    // ...
    <div>
      {sizeVariants.map((size, index) => (
        <Link
          key={index}
          href={`?${new URLSearchParams({
            color: selectedColor,
            size,
          })}`}
          className={`${
            selectedSize === size ? 'bg-blue-500' : 'bg-gray-200'
          } mr-2 inline-block h-8 w-8 text-center leading-8`}
        >
          {size}
        </Link>
      ))}
    </div>
    // ...
  );
}

This component accepts a prop named searchParams, which is an object containing key-value pairs representing the parameters from the URL query string. The selectedColor and selectedSize variables are extracted from these parameters.

Suppose you have a product page for a pink shirt in size ‘XL’. The URL might look like this:


https://www.examplestore.com/product-page?color=pink&size=XL

Now, let’s break down how the code contributes to this:

1 — Default Values:

In essence, default values act as a safety net, ensuring that the component gracefully handles diverse scenarios and remains robust in the face of potential irregularities in the data it receives.


const selectedColor = (searchParams?.color || 'blue') as string;
const selectedSize = (searchParams?.size || 'M') as string;

2 — Link Generation:

This line constructs the href attribute for each link, creating a new URLSearchParams object with the selected color and the specific size.

By incorporating new URLSearchParams, developers can create URLs that adhere to standards, ensuring compatibility across different browsers


href={`?${new URLSearchParams({ color: selectedColor, size })}`}

3 — Styling:

The class names dynamically style the links, highlighting the selected size with a blue background and providing a visual cue.


className={`${
  selectedSize === size ? 'bg-blue-500' : 'bg-gray-200'
} mr-2 inline-block h-8 w-8 text-center leading-8`}

Let’s explore an example on the client side featuring a search input.


'use client';

import { MagnifyingGlassIcon } from '@heroicons/react/24/outline';
import { useSearchParams, usePathname, useRouter } from 'next/navigation';
 
export default function Search() {
  const searchParams = useSearchParams();
  const pathname = usePathname();
  const { replace } = useRouter();
 
  function handleSearch(term: string) {
    const params = new URLSearchParams(searchParams);
    if (term) {
      params.set('query', term);
    } else {
      params.delete('query');
    }
    replace(`${pathname}?${params.toString()}`);
  }
}


return (
    <div className="relative flex flex-1 flex-shrink-0">
      <label htmlFor="search" className="sr-only">
        Search
      </label>
      <input
        className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
        placeholder={placeholder}
        defaultValue={searchParams.get('query')?.toString()}
        onChange={(e) => {
          handleSearch(e.target.value);
        }}
      />
      <MagnifyingGlassIcon className="absolute left-3 top-1/2 h-[18px] w-[18px] -translate-y-1/2 text-gray-500 peer-focus:text-gray-900" />
    </div>
  );
}

These lines use Next.js 14 client-side navigation hooks. useSearchParams retrieves the current URL parameters, usePathname gets the current pathname, and useRouter provides routing functionalities. Even if you are in client-side and can use hooks like useState, we continue in the same line of the benefits that using URL as a state manager provides.

1 —handleSearch:

The handleSearch function updates the URL parameters based on the provided search term. It utilizes a URLSearchParams object to manage the parameters, setting or deleting the 'query' parameter based on whether a term is provided. Finally, it uses replace to update the URL.


function handleSearch(term: string) {
    const params = new URLSearchParams(searchParams);
    if (term) {
      params.set('query', term);
    } else {
      params.delete('query');
    }
    replace(`${pathname}?${params.toString()}`);
  }

2 — defaultValue:

searchParams.get('query')?.toString() retrieves the value of the 'query' parameter from the URL. If the parameter exists, it returns the value; otherwise, it returns null.


<input
        className="peer block w-full rounded-md border border-gray-200 py-[9px] pl-10 text-sm outline-2 placeholder:text-gray-500"
        placeholder={placeholder}
        defaultValue={searchParams.get('query')?.toString()}
        onChange={(e) => {
          handleSearch(e.target.value);
        }}
      />

Sharing Curated Experiences with Ease:

One of the most compelling aspects of using the URL as a state manager is the seamless ability to share curated experiences effortlessly. Imagine a user exploring an online store’s vibrant collection of XL-sized, pink-hued products. With the URL serving as the state manager, the experience becomes dynamic and easily shareable.

Take, for instance, the URL: https://www.examplestore.com/product-page?color=pink&size=XL. This link encapsulates a specific state within the application – in this case, showcasing products tailored to the user's preference for pink color and XL size. The magic happens when this link is shared.

Here’s a list of examples showcasing how the URL can serve as an ideal state manager, particularly in the context of server components:

Pagination: /products?page=2

Search Inputs: /search?query=shoes&type=sneaker

Sorting Options: /products?category=clothing&sort=price-asc

Filtering by Category: /products?category=electronics

User Preferences: /profile?theme=dark&language=en

Dynamic Forms: /form?step=2

Conclusion

The examples showcased here represent just the tip of the iceberg when it comes to leveraging the URL as a powerful state manager in Next.js 14 server and client components. The beauty lies in the simplicity and flexibility of this approach, offering developers a canvas on which they can unleash their creativity.

Mauro Davoli

Frontend Engineer

Nov 24, 2023

5 min read

Next.js

Tools

Frontend