Next.js V13: revalidate not triggering after router.push

2 min read 05-10-2024
Next.js V13: revalidate not triggering after router.push


Next.js V13: Why revalidate Isn't Triggering After router.push

Problem: You're using Next.js V13's revalidate feature for server-side rendering (SSR) data fetching, but you're finding that revalidate isn't working as expected after using router.push to navigate between pages. This can lead to stale data being displayed on subsequent pages.

Simplified Explanation: Imagine you have a page showing a list of products. After clicking on a product, you're redirected to its detail page. However, even though the product data should be fresh, it might still display outdated information. This is because the revalidate mechanism wasn't properly triggered by the router.push action.

Scenario and Code:

Let's consider a simple example where we have a product list page (/products) and a product detail page (/products/[id]).

// pages/products/index.js
import { useState } from 'react';

const ProductsPage = async () => {
  const [products, setProducts] = useState([]);

  const fetchProducts = async () => {
    const res = await fetch('/api/products');
    const data = await res.json();
    setProducts(data);
  };

  useEffect(() => {
    fetchProducts();
  }, []);

  return (
    <div>
      <h2>Products</h2>
      <ul>
        {products.map((product) => (
          <li key={product.id}>
            <Link href={`/products/${product.id}`}>
              {product.name}
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
};

export default ProductsPage;

// pages/products/[id].js
import { useRouter } from 'next/navigation';

const ProductDetailPage = async ({ params }) => {
  const router = useRouter();
  const [product, setProduct] = useState(null);

  const fetchProduct = async () => {
    const res = await fetch(`/api/products/${params.id}`);
    const data = await res.json();
    setProduct(data);
  };

  useEffect(() => {
    fetchProduct();
  }, []);

  return (
    <div>
      <h2>{product?.name}</h2>
      {/* ... */}
    </div>
  );
};

export default ProductDetailPage;

// pages/api/products/[id].js
export default async function handler(req, res) {
  const id = req.query.id;
  // Simulating data fetching from a database
  const product = await fetch(`https://api.example.com/products/${id}`);
  const data = await product.json();
  res.status(200).json(data);
}

Analysis and Clarification:

  • The problem: The revalidate mechanism in Next.js V13 is designed to automatically re-fetch data for server-side rendered pages when there's a change in the underlying data. However, this mechanism isn't triggered by client-side navigation using router.push.
  • Solution: You can manually trigger revalidate after the router.push action using the router.refresh() function. This will revalidate the current page and any other pages that depend on the same data.
  • Example:
// pages/products/[id].js
import { useRouter } from 'next/navigation';

const ProductDetailPage = async ({ params }) => {
  const router = useRouter();
  // ...

  const fetchProduct = async () => {
    // ... fetch product data
  };

  useEffect(() => {
    fetchProduct();
    router.refresh(); // Manually trigger revalidation
  }, []);

  // ... rest of the component
};

export default ProductDetailPage;

Additional Value:

  • By manually triggering revalidate after navigation, you ensure that stale data isn't displayed on subsequent pages.
  • This approach is particularly useful when dealing with dynamic routes and pages that rely on data that might be changing frequently.

References and Resources:

Conclusion:

Understanding the interplay between revalidate and router.push in Next.js V13 is crucial for creating efficient and data-driven applications. Manually triggering revalidate after navigation ensures that your data remains up-to-date and improves the overall user experience.