How can I force a certain page to revalidate from a client component in NextJS?
Asked Answered
D

5

5

The problem

I'm making a website with a supabase backend, where people can make things called guides. Each guide has a dedicated dynamic path /guides/[id] and a dedicated edit page /guides/[id]/edit, which contains the form for updating the data in the guide. On submitting the form, I use router.push() from next/navigation to navigate back to /guides/[id] after making the requests to supabase. However, when it navigates back to /guides/[id], it still shows the old cached data - I need to refresh the page to get the updated data.

I don't want to revalidate the page every time it's visited, I only want to revalidate it whenever the form is submitted. Is there a way to do that?

Attempted solutions

I looked at the NextJS docs for revalidatePath. I tried using the function directly in my solution like this:

...
revalidatePath(`/guides/${id}`);
router.push(`/guides/${id}`);
...

But ended up with this error:

Error: Invariant: static generation store missing in revalidateTag _N_T_/guides/18

leading me to believe it could only be called server-side. So, I tried creating an api route /api/revalidate/route.ts like so:

// /api/revalidate/route.ts
import { revalidatePath } from "next/cache";
import { NextRequest } from "next/server";

export async function GET(request: NextRequest) {
  const path = request.nextUrl.searchParams.get("path");

  if (path) {
    revalidatePath(path);
    return Response.json({ revalidated: true, now: Date.now() });
  }

  return Response.json({
    revalidated: false,
    now: Date.now(),
    message: "Missing path to revalidate",
  });
}
// /guide/[id]/edit.tsx
...
await fetch(`/api/revalidate?path=/guides/${id}`);
router.push(`/guides/${id}`);
...

But that didn't work either. What am I doing wrong here?

Dealing answered 7/12, 2023 at 9:22 Comment(0)
C
6

you are right revalidatePath function can only be called server-side Source.
So let's explore some options:

One possible reason for the error is that revalidatePath function is not triggering a re-validation as we expect. The re-validation will happen on the next request to the page after the mark is set Source.

Another possible reason could be that the revalidatePath function is not able to find the path in the cache. i would ask you to check if the path you're passing to revalidatePath matches the correct path Source.

Lastly try using router.refresh method to force a refresh of the page after the form submission.Source, Source.

i wrote an example:

import { useRouter } from 'next/router';

const MyComponent = () => {
 const router = useRouter();

 const handleFormSubmit = async () => {
   // Perform your form submission logic here...

   // After the form submission, refresh the page
   router.refresh();
 };

 return (
   <form onSubmit={handleFormSubmit}>
     {/* Your form fields here... */}
   </form>
 );
};

export default MyComponent;

handleFormSubmit is the function that gets called when the form is submitted and then it refresh the page.

Claudclauddetta answered 7/12, 2023 at 9:40 Comment(3)
Thank you! Forcing the router to refresh before navigating back did the trick.Dealing
i'm glad it worked!! i must point out that router.refresh will slow the user in navigating your webpageClaudclauddetta
True, but considering I'm only doing this when a form is being submitted, I'm willing to allow that occasional slowdownDealing
S
5

This worked for me,

import { useRouter } from "next/navigation";

const MyComponent = () => {
 const router = useRouter();

 const handleFormSubmit = async () => {
   // Perform your form submission logic here...

   router.push(`/guides/${id}`);
   router.refresh();
 };

 return (
   <form onSubmit={handleFormSubmit}>
     {/* Your form fields here... */}
   </form>
 );
};

export default MyComponent;
Soever answered 12/4 at 14:49 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Richia
O
2

Encountered the same issue , but found a much simpler solution , we can use search Params as it marks the route as dynamic route.

After this setup i would do

  router.push("/your/route?new=1");

This would make sure that your page renders dynamically when the request is made.

Orectic answered 17/2 at 13:53 Comment(3)
Nice trick! I'll try using that in case I run into the problem again.Dealing
Apparently you can also do router.push("/your/route?") and it still works. At least it did for me.Vannavannatta
I tried that but it didn't work for me, at least in dev. router.push('foo/route") and then router.refresh() does work though.Rilke
T
0

It is possible to revalidate a tag from within the client.

Create a server component that revalidates the tag:

"use server";

import { revalidateTag as revalidate } from "next/cache";

async function revalidateTag(name) {
    revalidate(name);
}

export default revalidateTag;

Then create a client component that triggers that tag revalidation function

"use client";

import React from "react";

import revalidateTag from "./revalidateTag";

function CurrencySelector() {
    function setCurrency() {
        // Set the currency in the cookies or state
        // And revalidate the prices
        revalidateTag("calculatePrices");
    }

    return <button onClick={() => setCurrency("EUR")}>Set currency to EUR</button>;
}

export default CurrencySelector;

The tag was defined in a fetch function

const response = await fetch(`blablabla/api/calculate-prices`, {
   method: "POST",
   headers: {
      "Content-Type": "application/json",
   },
   body: JSON.stringify({ dateStart: utcDateStart, dateEnd: utcDateEnd, propertyID, currency }),
   next: { tags: ["calculatePrices"] },
});
Tersina answered 6/5 at 8:16 Comment(0)
N
0

here is how you can revalidate certain pages

in my scenario, I have the URL /jobs/[slug]

when I try to revalidate the jobs using path it also revalidates the /jobs/[slug]

now what do I do i use a tag for revalidating the /jobs and I use path for revalidating the only specific updated slug so it will not revalidate all the jobs slugs

here is the code :

create the API route in api/revalidate/route.ts:

use this code:

import { NextResponse, NextRequest } from "next/server";
import { revalidatePath, revalidateTag } from "next/cache";

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.nextUrl);
  const path = searchParams.get("path");
  const tag = searchParams.get("tag");
  // const authHeader = req.headers.get('authorization');

  // if (!isAuthorized(authHeader)) {
  //   return NextResponse.json({ message: 'Unauthorized' }, { status: 401 });
  // }

  if (!path && !tag) {
    return NextResponse.json(
      { message: "Missing param path/tag" },
      { status: 400 }
    );
  }

  try {
    if (tag) {
      revalidateTag(tag);
      return NextResponse.json({ message: `Revalidated Tag: ${tag}` });
    }

    if (path) {
      revalidatePath(path);
    } else {
      return NextResponse.json(
        { message: "Missing param path" },
        { status: 400 }
      );
    }
    return NextResponse.json({ message: `Revalidated Path: ${path}` });
  } catch (error: any) {
    return NextResponse.json(
      { message: "Error revalidating", error: error.message },
      { status: 500 }
    );
  }
}

now in frontend where you need to call your API using fetch not Axios like this:

const url = process.env.NEXT_PUBLIC_API_URL;

export const getPublicSchema = async (tag: string) => {
  try {
    const response = await fetch(`${url}/your-api-end-point`, {
      next: { tags: [tag] },
    });
    const data = await response.json();
    return data;
  } catch (error: any) {
    console.error(error?.response?.data?.message);
    return [];
  }
};

now call the function in your page and make sure your page is async SSR:

like this:

const page = async () => {
  const jobsData = await getPublicSchema("pass-your-tag");
  return (
    <>
      <NewUiHomepageLayout>
        <PreLoginJobs jobsData={jobsData} />
      </NewUiHomepageLayout>

      <FooterSection />
    </>
  );
};

export default page;

Now create a revalidated API in the backend:

in my, I am using nest js in the backend:

/**
 * Triggers revalidation for a given path.
 *
 * This function initiates a revalidation process for the specified path.
 * It is an asynchronous function and returns a promise that resolves when
 * the revalidation is complete.
 * @param path - The path to revalidate.
 */
export async function triggerRevalidation(path: string): Promise<void> {
  // const revalidateUrl = `http://localhost:3000/api/revalidate?path=${path}`;
  const revalidateUrl = `${process.env.FRONTEND_URL}/api/revalidate?path=${path}`;

  try {
    const response = await fetch(revalidateUrl, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
      },
    });

    console.log('Revalidation response:', await response.json());

    if (!response.ok) {
      console.error(`Revalidation failed: ${response.statusText}`);
    } else {
      console.log('Revalidation triggered successfully', path);
    }
  } catch (error) {
    console.error('Error triggering revalidation:', error);
  }
}

now call this API where you are calling your route: like this:

/**
   * Creates a new public jobs meta entry.
   * @param createPublicJobsMetaDto - The data transfer object containing the details of the public jobs meta to create.
   * @returns A promise that resolves to the created public jobs meta.
   * @throws ConflictException if the slug is not unique.
   */
  @Admin()
  @Post()
  async create(@Body() createPublicJobsMetaDto: CreatePublicJobsMetaDto): Promise<any> {
    const newMeta = await this.publicJobsMetaService.create(createPublicJobsMetaDto);
    await triggerRevalidation('/sitemap-job.xml');
    await triggerRevalidationTag('public-jobs-meta-tag');
    return newMeta;
  }

want to hire me connect with me:

https://www.linkedin.com/in/saleem-raza/ email: [email protected]

Novelia answered 5/11 at 10:2 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.