Skip to content

Added revalidation docs #1444

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Nov 1, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 145 additions & 1 deletion docs/guides/frameworks/nextjs.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -248,12 +248,156 @@ Here are the steps to trigger your task in the Next.js App and Pages router and

<DeployingYourTask />

## Troubleshooting
## Troubleshooting & extra resources

<NextjsTroubleshootingMissingApiKey/>
<NextjsTroubleshootingButtonSyntax/>
<WorkerFailedToStartWhenRunningDevCommand/>

### Revalidation from your Trigger.dev tasks

[Revalidation](https://vercel.com/docs/incremental-static-regeneration/quickstart#on-demand-revalidation) allows you to purge the cache for an ISR route. To revalidate an ISR route from a Trigger.dev task, you have to set up a handler for the `revalidate` event. This is an API route that you can add to your Next.js app.

This handler will run the `revalidatePath` function from Next.js, which purges the cache for the given path.

The handlers are slightly different for the App and Pages router:

#### Revalidation handler: App Router

If you are using the App router, create a new revalidation route at `app/api/revalidate/path/route.ts`:

```ts app/api/revalidate/path/route.ts
import { NextRequest, NextResponse } from "next/server";
import { revalidatePath } from "next/cache";

export async function POST(request: NextRequest) {
try {
const { path, type, secret } = await request.json();
// Create a REVALIDATION_SECRET and set it in your environment variables
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ message: "Invalid secret" }, { status: 401 });
}

if (!path) {
return NextResponse.json({ message: "Path is required" }, { status: 400 });
}

revalidatePath(path, type);

return NextResponse.json({ revalidated: true });
} catch (err) {
console.error("Error revalidating path:", err);
return NextResponse.json({ message: "Error revalidating path" }, { status: 500 });
}
}
```
Comment on lines +269 to +293
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance type safety and error handling in the App Router handler.

Consider these improvements to align with best practices:

  1. Add proper type definitions for the request body
  2. Enhance error handling with specific error types
  3. Document the optional type parameter

Apply these improvements:

+type RevalidateRequestBody = {
+  path: string;
+  type?: "page" | "layout";  // Optional parameter to specify revalidation type
+  secret: string;
+};

 export async function POST(request: NextRequest) {
   try {
-    const { path, type, secret } = await request.json();
+    const body = await request.json() as RevalidateRequestBody;
+    const { path, type, secret } = body;

     if (secret !== process.env.REVALIDATION_SECRET) {
       return NextResponse.json({ message: "Invalid secret" }, { status: 401 });
     }

     if (!path) {
       return NextResponse.json({ message: "Path is required" }, { status: 400 });
     }

     revalidatePath(path, type);

     return NextResponse.json({ revalidated: true });
   } catch (err) {
-    console.error("Error revalidating path:", err);
+    console.error(`Error revalidating path: ${err instanceof Error ? err.message : String(err)}`);
     return NextResponse.json({ message: "Error revalidating path" }, { status: 500 });
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```ts app/api/revalidate/path/route.ts
import { NextRequest, NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
export async function POST(request: NextRequest) {
try {
const { path, type, secret } = await request.json();
// Create a REVALIDATION_SECRET and set it in your environment variables
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ message: "Invalid secret" }, { status: 401 });
}
if (!path) {
return NextResponse.json({ message: "Path is required" }, { status: 400 });
}
revalidatePath(path, type);
return NextResponse.json({ revalidated: true });
} catch (err) {
console.error("Error revalidating path:", err);
return NextResponse.json({ message: "Error revalidating path" }, { status: 500 });
}
}
```
```ts app/api/revalidate/path/route.ts
import { NextRequest, NextResponse } from "next/server";
import { revalidatePath } from "next/cache";
type RevalidateRequestBody = {
path: string;
type?: "page" | "layout"; // Optional parameter to specify revalidation type
secret: string;
};
export async function POST(request: NextRequest) {
try {
const body = await request.json() as RevalidateRequestBody;
const { path, type, secret } = body;
if (secret !== process.env.REVALIDATION_SECRET) {
return NextResponse.json({ message: "Invalid secret" }, { status: 401 });
}
if (!path) {
return NextResponse.json({ message: "Path is required" }, { status: 400 });
}
revalidatePath(path, type);
return NextResponse.json({ revalidated: true });
} catch (err) {
console.error(`Error revalidating path: ${err instanceof Error ? err.message : String(err)}`);
return NextResponse.json({ message: "Error revalidating path" }, { status: 500 });
}
}
```


#### Revalidation handler: Pages Router

If you are using the Pages router, create a new revalidation route at `pages/api/revalidate/path.ts`:

```ts pages/api/revalidate/path.ts
import type { NextApiRequest, NextApiResponse } from "next";

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (req.method !== "POST") {
return res.status(405).json({ message: "Method not allowed" });
}

const { path, secret } = req.body;

if (secret !== process.env.REVALIDATION_SECRET) {
return res.status(401).json({ message: "Invalid secret" });
}

if (!path) {
return res.status(400).json({ message: "Path is required" });
}

await res.revalidate(path);

return res.json({ revalidated: true });
} catch (err) {
console.error("Error revalidating path:", err);
return res.status(500).json({ message: "Error revalidating path" });
}
}
```
Comment on lines +299 to +326
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance type safety and error handling in the Pages Router handler.

Consider these improvements to align with best practices:

  1. Add type restriction for HTTP methods
  2. Add proper type definitions for the request body
  3. Enhance error handling with specific error types

Apply these improvements:

+type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
+interface RevalidateRequest extends NextApiRequest {
+  method: HttpMethod;
+  body: {
+    path: string;
+    secret: string;
+  };
+}

-export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+export default async function handler(req: RevalidateRequest, res: NextApiResponse) {
   try {
     if (req.method !== "POST") {
       return res.status(405).json({ message: "Method not allowed" });
     }

     const { path, secret } = req.body;

     if (secret !== process.env.REVALIDATION_SECRET) {
       return res.status(401).json({ message: "Invalid secret" });
     }

     if (!path) {
       return res.status(400).json({ message: "Path is required" });
     }

     await res.revalidate(path);

     return res.json({ revalidated: true });
   } catch (err) {
-    console.error("Error revalidating path:", err);
+    console.error(`Error revalidating path: ${err instanceof Error ? err.message : String(err)}`);
     return res.status(500).json({ message: "Error revalidating path" });
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
```ts pages/api/revalidate/path.ts
import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
if (req.method !== "POST") {
return res.status(405).json({ message: "Method not allowed" });
}
const { path, secret } = req.body;
if (secret !== process.env.REVALIDATION_SECRET) {
return res.status(401).json({ message: "Invalid secret" });
}
if (!path) {
return res.status(400).json({ message: "Path is required" });
}
await res.revalidate(path);
return res.json({ revalidated: true });
} catch (err) {
console.error("Error revalidating path:", err);
return res.status(500).json({ message: "Error revalidating path" });
}
}
```
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
interface RevalidateRequest extends NextApiRequest {
method: HttpMethod;
body: {
path: string;
secret: string;
};
}
export default async function handler(req: RevalidateRequest, res: NextApiResponse) {
try {
if (req.method !== "POST") {
return res.status(405).json({ message: "Method not allowed" });
}
const { path, secret } = req.body;
if (secret !== process.env.REVALIDATION_SECRET) {
return res.status(401).json({ message: "Invalid secret" });
}
if (!path) {
return res.status(400).json({ message: "Path is required" });
}
await res.revalidate(path);
return res.json({ revalidated: true });
} catch (err) {
console.error(`Error revalidating path: ${err instanceof Error ? err.message : String(err)}`);
return res.status(500).json({ message: "Error revalidating path" });
}
}


#### Revalidation task

This task takes a `path` as a payload and will revalidate the path you specify, using the handler you set up previously.

<Note>

To run this task locally you will need to set the `REVALIDATION_SECRET` environment variable in your `.env.local` file (or `.env` file if using Pages router).

To run this task in production, you will need to set the `REVALIDATION_SECRET` environment variable in Vercel, in your project settings, and also in your environment variables in the Trigger.dev dashboard.

</Note>

```ts trigger/revalidate-path.ts
import { logger, task } from "@trigger.dev/sdk/v3";

const NEXTJS_APP_URL = process.env.NEXTJS_APP_URL; // e.g. "http://localhost:3000" or "https://my-nextjs-app.vercel.app"
const REVALIDATION_SECRET = process.env.REVALIDATION_SECRET; // Create a REVALIDATION_SECRET and set it in your environment variables

export const revalidatePath = task({
id: "revalidate-path",
run: async (payload: { path: string }) => {
const { path } = payload;

try {
const response = await fetch(`${NEXTJS_APP_URL}/api/revalidate/path`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
path: `${NEXTJS_APP_URL}/${path}`,
secret: REVALIDATION_SECRET,
}),
});

if (response.ok) {
logger.log("Path revalidation successful", { path });
return { success: true };
} else {
logger.error("Path revalidation failed", {
path,
statusCode: response.status,
statusText: response.statusText,
});
return {
success: false,
error: `Revalidation failed with status ${response.status}: ${response.statusText}`,
};
}
} catch (error) {
logger.error("Path revalidation encountered an error", {
path,
error: error instanceof Error ? error.message : String(error),
});
return {
success: false,
error: `Failed to revalidate path due to an unexpected error`,
};
}
},
});
```
Comment on lines +328 to +389
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve URL handling and environment validation.

The implementation needs some improvements:

  1. URL Construction: The current implementation might create URLs with double slashes. Use URL class instead.
  2. Environment Variables: Add validation at startup.
  3. Network Failures: Consider adding retry logic.

Apply these improvements:

+ // Validate environment variables at startup
+ if (!NEXTJS_APP_URL) {
+   throw new Error("NEXTJS_APP_URL environment variable is required");
+ }
+ if (!REVALIDATION_SECRET) {
+   throw new Error("REVALIDATION_SECRET environment variable is required");
+ }

  run: async (payload: { path: string }) => {
    const { path } = payload;

+   // Ensure clean URL construction
+   const baseUrl = new URL("/api/revalidate/path", NEXTJS_APP_URL);
+   const pathToRevalidate = new URL(path, NEXTJS_APP_URL).pathname;

    try {
-     const response = await fetch(`${NEXTJS_APP_URL}/api/revalidate/path`, {
+     const response = await fetch(baseUrl.toString(), {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
-         path: `${NEXTJS_APP_URL}/${path}`,
+         path: pathToRevalidate,
          secret: REVALIDATION_SECRET,
        }),
      });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
#### Revalidation task
This task takes a `path` as a payload and will revalidate the path you specify, using the handler you set up previously.
<Note>
To run this task locally you will need to set the `REVALIDATION_SECRET` environment variable in your `.env.local` file (or `.env` file if using Pages router).
To run this task in production, you will need to set the `REVALIDATION_SECRET` environment variable in Vercel, in your project settings, and also in your environment variables in the Trigger.dev dashboard.
</Note>
```ts trigger/revalidate-path.ts
import { logger, task } from "@trigger.dev/sdk/v3";
const NEXTJS_APP_URL = process.env.NEXTJS_APP_URL; // e.g. "http://localhost:3000" or "https://my-nextjs-app.vercel.app"
const REVALIDATION_SECRET = process.env.REVALIDATION_SECRET; // Create a REVALIDATION_SECRET and set it in your environment variables
export const revalidatePath = task({
id: "revalidate-path",
run: async (payload: { path: string }) => {
const { path } = payload;
try {
const response = await fetch(`${NEXTJS_APP_URL}/api/revalidate/path`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
path: `${NEXTJS_APP_URL}/${path}`,
secret: REVALIDATION_SECRET,
}),
});
if (response.ok) {
logger.log("Path revalidation successful", { path });
return { success: true };
} else {
logger.error("Path revalidation failed", {
path,
statusCode: response.status,
statusText: response.statusText,
});
return {
success: false,
error: `Revalidation failed with status ${response.status}: ${response.statusText}`,
};
}
} catch (error) {
logger.error("Path revalidation encountered an error", {
path,
error: error instanceof Error ? error.message : String(error),
});
return {
success: false,
error: `Failed to revalidate path due to an unexpected error`,
};
}
},
});
```
#### Revalidation task
This task takes a `path` as a payload and will revalidate the path you specify, using the handler you set up previously.
<Note>
To run this task locally you will need to set the `REVALIDATION_SECRET` environment variable in your `.env.local` file (or `.env` file if using Pages router).
To run this task in production, you will need to set the `REVALIDATION_SECRET` environment variable in Vercel, in your project settings, and also in your environment variables in the Trigger.dev dashboard.
</Note>
```ts trigger/revalidate-path.ts
import { logger, task } from "@trigger.dev/sdk/v3";
const NEXTJS_APP_URL = process.env.NEXTJS_APP_URL; // e.g. "http://localhost:3000" or "https://my-nextjs-app.vercel.app"
const REVALIDATION_SECRET = process.env.REVALIDATION_SECRET; // Create a REVALIDATION_SECRET and set it in your environment variables
// Validate environment variables at startup
if (!NEXTJS_APP_URL) {
throw new Error("NEXTJS_APP_URL environment variable is required");
}
if (!REVALIDATION_SECRET) {
throw new Error("REVALIDATION_SECRET environment variable is required");
}
export const revalidatePath = task({
id: "revalidate-path",
run: async (payload: { path: string }) => {
const { path } = payload;
// Ensure clean URL construction
const baseUrl = new URL("/api/revalidate/path", NEXTJS_APP_URL);
const pathToRevalidate = new URL(path, NEXTJS_APP_URL).pathname;
try {
const response = await fetch(baseUrl.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
path: pathToRevalidate,
secret: REVALIDATION_SECRET,
}),
});
if (response.ok) {
logger.log("Path revalidation successful", { path });
return { success: true };
} else {
logger.error("Path revalidation failed", {
path,
statusCode: response.status,
statusText: response.statusText,
});
return {
success: false,
error: `Revalidation failed with status ${response.status}: ${response.statusText}`,
};
}
} catch (error) {
logger.error("Path revalidation encountered an error", {
path,
error: error instanceof Error ? error.message : String(error),
});
return {
success: false,
error: `Failed to revalidate path due to an unexpected error`,
};
}
},
});
```

Comment on lines +346 to +389
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance type safety and input validation.

Consider adding input validation and improving type safety:

  1. Define a proper type for the payload
  2. Add validation for the path format

Apply these improvements:

+type RevalidatePathPayload = {
+  path: string;
+};
+
+function isValidPath(path: string): boolean {
+  return path.length > 0 && !path.includes('..') && !path.startsWith('/');
+}
+
 export const revalidatePath = task({
   id: "revalidate-path",
-  run: async (payload: { path: string }) => {
+  run: async (payload: RevalidatePathPayload) => {
     const { path } = payload;
+
+    if (!isValidPath(path)) {
+      logger.error("Invalid path format", { path });
+      return {
+        success: false,
+        error: "Invalid path format. Path should not be empty, contain '..' or start with '/'",
+      };
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const revalidatePath = task({
id: "revalidate-path",
run: async (payload: { path: string }) => {
const { path } = payload;
try {
const response = await fetch(`${NEXTJS_APP_URL}/api/revalidate/path`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
path: `${NEXTJS_APP_URL}/${path}`,
secret: REVALIDATION_SECRET,
}),
});
if (response.ok) {
logger.log("Path revalidation successful", { path });
return { success: true };
} else {
logger.error("Path revalidation failed", {
path,
statusCode: response.status,
statusText: response.statusText,
});
return {
success: false,
error: `Revalidation failed with status ${response.status}: ${response.statusText}`,
};
}
} catch (error) {
logger.error("Path revalidation encountered an error", {
path,
error: error instanceof Error ? error.message : String(error),
});
return {
success: false,
error: `Failed to revalidate path due to an unexpected error`,
};
}
},
});
```
type RevalidatePathPayload = {
path: string;
};
function isValidPath(path: string): boolean {
return path.length > 0 && !path.includes('..') && !path.startsWith('/');
}
export const revalidatePath = task({
id: "revalidate-path",
run: async (payload: RevalidatePathPayload) => {
const { path } = payload;
if (!isValidPath(path)) {
logger.error("Invalid path format", { path });
return {
success: false,
error: "Invalid path format. Path should not be empty, contain '..' or start with '/'",
};
}
try {
const response = await fetch(`${NEXTJS_APP_URL}/api/revalidate/path`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
path: `${NEXTJS_APP_URL}/${path}`,
secret: REVALIDATION_SECRET,
}),
});
if (response.ok) {
logger.log("Path revalidation successful", { path });
return { success: true };
} else {
logger.error("Path revalidation failed", {
path,
statusCode: response.status,
statusText: response.statusText,
});
return {
success: false,
error: `Revalidation failed with status ${response.status}: ${response.statusText}`,
};
}
} catch (error) {
logger.error("Path revalidation encountered an error", {
path,
error: error instanceof Error ? error.message : String(error),
});
return {
success: false,
error: `Failed to revalidate path due to an unexpected error`,
};
}
},
});


#### Testing the revalidation task

You can test your revalidation task in the Trigger.dev dashboard on the testing page, using the following payload.

```json
{
"path": "<path-to-revalidate>" // e.g. "blog"
}
```

## Additional resources for Next.js

<Card
Expand Down