Managing State in Next.js using URL Parameters

I've come to appreciate the power and simplicity of URL parameters for state management. In this article, I'll explain why URL parameters are often better than traditional state management techniques (like useState).

We'll explore this approach using the latest version of React and demonstrate its implementation in both server and client components.

Why URL Parameters?

Before diving into the implementation, let's consider why URL parameters can be advantageous:

  • Shareable State: Unlike local component state, URL parameters make your application state shareable. Users can copy and paste URLs to share specific application states with others.

  • Browser History Integration: URL parameters naturally integrate with the browser's history, allowing users to navigate back and forth between different application states.

  • Server-Side Rendering Friendly: Next.js is best when using SSR, and URL parameters are readily available on the server, making it easier to render the initial state.

  • Reduced Component Complexity: Moving state to the URL can often simplify our component hierarchy and reduce prop drilling.

  • Persistence: URL parameters persist even when the user refreshes the page, providing a consistent user experience.

Let's see how we can implement this approach in Next.js using server and client components. These all assume you are using a modern flavor of Next.js (13+).

SSR Example

seachParams are auto-magically given to us as props in page components in Next.js. So there's no router or other things needed. Simply pull the parameters off the props:

// app/products/page.js
import { notFound } from 'next/navigation';

async function fetchProductData(category, sort) {
  // Simulate API call
  const res = await fetch(`https://api.example.com/products?category=${category}&sort=${sort}`);
  if (!res.ok) return null;
  return res.json();
}

export default async function ProductPage({ searchParams }) {
  const category = searchParams.category || 'all';
  const sort = searchParams.sort || 'newest';

  const products = await fetchProductData(category, sort);

  if (!products) notFound();

  return (
    <div>
      <h1>Products in {category}</h1>
      <p>Sorted by: {sort}</p>
      {/* Render product list */}
    </div>
  );
}

This approach allows us to fetch data on the server based on these parameters, enabling efficient server-side rendering. It also means users could share their search filters with a friend by sharing the complete URL.

Just ensure you are using a page.js file as you don't get the searchParams prop in the layout.js or other SSR components outside the page and will need to rely on passing props down.

Another option is using client components which we will explore next:

Client Component Example

Alright, so we won't always want SSR things, but thankfully, URL parameters in client components are also easy to grab and use.

Using next/navigation we can update our parameters using useRouter and read the parameters using useSearchParams.

Here's an example of how to do it:

functionality:

"use client";

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

export default function ProductFilters() {
  const router = useRouter();
  const searchParams = useSearchParams();

  const onFilterChange = (e) => {
    const params = new URLSearchParams(searchParams);
    params.set("category", e.target.value);
    router.push(`/products?${params.toString()}`);
  };

  const onSortChange = (e) => {
    const params = new URLSearchParams(searchParams);
    params.set("sort", e.target.value);
    router.push(`/products?${params.toString()}`);
  };

  return (
    <div>
      <select onChange={onFilterChange} defaultValue={searchParams.get("category") || "all"}>
        <option value="all">All Categories</option>
        <option value="electronics">Electronics</option>
        <option value="books">Books</option>
      </select>
      <select onChange={onSortChange} defaultValue={searchParams.get("sort") || "newest"}>
        <option value="newest">Newest</option>
        <option value="price-asc">Price: Low to High</option>
        <option value="price-desc">Price: High to Low</option>
      </select>
    </div>
  );
}

In this example, when a user changes a filter or sort option, we update the URL, which triggers a re-render of our page with the new state.

This method isn't always the best choice—for complex, nested state, or sensitive data, you might want to consider other state management solutions. However, URL parameters provide an excellent solution for many use cases, especially those involving filterable or sortable content.

Give it a try in your next project and see how it can improve your development and application's user experience!

ReactjsNextjs
Avatar for Niall Maher

Written by Niall Maher

Founder of Codú - The web developer community! I've worked in nearly every corner of technology businesses: Lead Developer, Software Architect, Product Manager, CTO, and now happily a Founder.

Loading

Fetching comments

Hey! 👋

Got something to say?

or to leave a comment.