diff --git a/.changeset/gorgeous-cycles-cheat.md b/.changeset/gorgeous-cycles-cheat.md
new file mode 100644
index 0000000000..d13c27765a
--- /dev/null
+++ b/.changeset/gorgeous-cycles-cheat.md
@@ -0,0 +1,5 @@
+---
+"gitbook-v2": patch
+---
+
+add a force-revalidate api route to force bust the cache in case of errors
diff --git a/packages/gitbook-v2/next-env.d.ts b/packages/gitbook-v2/next-env.d.ts
index 1b3be0840f..3cd7048ed9 100644
--- a/packages/gitbook-v2/next-env.d.ts
+++ b/packages/gitbook-v2/next-env.d.ts
@@ -1,5 +1,6 @@
///
///
+///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/packages/gitbook-v2/src/pages/api/~gitbook/force-revalidate.ts b/packages/gitbook-v2/src/pages/api/~gitbook/force-revalidate.ts
new file mode 100644
index 0000000000..45e6c7eca1
--- /dev/null
+++ b/packages/gitbook-v2/src/pages/api/~gitbook/force-revalidate.ts
@@ -0,0 +1,49 @@
+import crypto from 'node:crypto';
+import type { NextApiRequest, NextApiResponse } from 'next';
+
+interface JsonBody {
+ // The paths need to be the rewritten one, `res.revalidate` call don't go through the middleware
+ paths: string[];
+}
+
+export default async function handler(req: NextApiRequest, res: NextApiResponse) {
+ // Only allow POST requests
+ if (req.method !== 'POST') {
+ return res.status(405).json({ error: 'Method not allowed' });
+ }
+ const signatureHeader = req.headers['x-gitbook-signature'] as string | undefined;
+ if (!signatureHeader) {
+ return res.status(400).json({ error: 'Missing signature header' });
+ }
+ // We cannot use env from `@/v2/lib/env` here as it make it crash because of the import "server-only" in the file.
+ if (process.env.GITBOOK_SECRET) {
+ try {
+ const computedSignature = crypto
+ .createHmac('sha256', process.env.GITBOOK_SECRET)
+ .update(JSON.stringify(req.body))
+ .digest('hex');
+
+ if (computedSignature === signatureHeader) {
+ const results = await Promise.allSettled(
+ (req.body as JsonBody).paths.map((path) => {
+ // biome-ignore lint/suspicious/noConsole: we want to log here
+ console.log(`Revalidating path: ${path}`);
+ return res.revalidate(path);
+ })
+ );
+ return res.status(200).json({
+ success: results.every((result) => result.status === 'fulfilled'),
+ errors: results
+ .filter((result) => result.status === 'rejected')
+ .map((result) => (result as PromiseRejectedResult).reason),
+ });
+ }
+ return res.status(401).json({ error: 'Invalid signature' });
+ } catch (error) {
+ console.error('Error during revalidation:', error);
+ return res.status(400).json({ error: 'Invalid request or unable to parse JSON' });
+ }
+ }
+ // If no secret is set, we do not allow revalidation
+ return res.status(403).json({ error: 'Revalidation is disabled' });
+}