Skip to content

Commit 905d46c

Browse files
authored
Merge pull request #51 from 9d8dev/brijr/plugin
Create WP-Revalidate Next.js Plugin!
2 parents dd8092e + 422a153 commit 905d46c

File tree

12 files changed

+1737
-454
lines changed

12 files changed

+1737
-454
lines changed

.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,6 @@
11
WORDPRESS_URL="https://wordpress.com"
22
WORDPRESS_HOSTNAME="wordpress.com"
3+
4+
# If using the revalidate plugin
5+
# You can generate by running `openssl rand -base64 32` in the terminal
6+
WORDPRESS_WEBHOOK_SECRET="your-secret-key-here"

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,5 @@ yarn-error.log*
3434
# typescript
3535
*.tsbuildinfo
3636
next-env.d.ts
37+
38+
CLAUDE.md

README.md

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -433,15 +433,15 @@ The WordPress API functions use a hierarchical cache tag system:
433433

434434
1. **Install the WordPress Plugin:**
435435

436-
- Navigate to `wordpress/next-revalidate/`
437-
- Create a zip file of the folder
436+
- Navigate to the `/plugin` directory
437+
- Use the pre-built `next-revalidate.zip` file or create a ZIP from the `next-revalidate` folder
438438
- Install and activate through WordPress admin
439439
- Go to Settings > Next.js Revalidation
440440
- Configure your Next.js URL and webhook secret
441441

442442
2. **Configure Next.js:**
443443

444-
- Add `WORDPRESS_WEBHOOK_SECRET` to your environment variables
444+
- Add `WORDPRESS_WEBHOOK_SECRET` to your environment variables (same secret as in WordPress plugin)
445445
- The webhook endpoint at `/api/revalidate` is already set up
446446
- No additional configuration needed
447447

@@ -451,6 +451,16 @@ The WordPress API functions use a hierarchical cache tag system:
451451
- Next.js automatically revalidates the appropriate cache tags
452452
- Only affected content is updated, maintaining performance
453453

454+
### Plugin Features
455+
456+
The Next.js Revalidation plugin includes:
457+
458+
- Automatic revalidation when posts, pages, categories, tags, authors, or media are modified
459+
- Settings page to configure your Next.js site URL and webhook secret
460+
- Manual revalidation option for full site refresh
461+
- Support for custom post types and taxonomies
462+
- Optional admin notifications for revalidation events
463+
454464
### Manual Revalidation
455465

456466
You can also manually revalidate content using the `revalidateWordPressData` function:

app/api/revalidate/route.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { revalidateTag } from "next/cache";
2+
import { NextRequest, NextResponse } from "next/server";
3+
4+
export async function POST(request: NextRequest) {
5+
try {
6+
const requestBody = await request.json();
7+
const secret = request.headers.get("x-webhook-secret");
8+
9+
// Validate webhook secret
10+
if (secret !== process.env.WORDPRESS_WEBHOOK_SECRET) {
11+
console.error("Invalid webhook secret");
12+
return NextResponse.json(
13+
{ message: "Invalid webhook secret" },
14+
{ status: 401 }
15+
);
16+
}
17+
18+
// Extract content type and ID from the webhook payload
19+
const { contentType, contentId } = requestBody;
20+
21+
if (!contentType) {
22+
return NextResponse.json(
23+
{ message: "Missing content type" },
24+
{ status: 400 }
25+
);
26+
}
27+
28+
// Determine which tags to revalidate
29+
const tagsToRevalidate = ["wordpress"];
30+
31+
// Add content type specific tag
32+
if (contentType === "post") {
33+
tagsToRevalidate.push("posts");
34+
if (contentId) {
35+
tagsToRevalidate.push(`post-${contentId}`);
36+
}
37+
} else if (contentType === "page") {
38+
tagsToRevalidate.push("pages");
39+
if (contentId) {
40+
tagsToRevalidate.push(`page-${contentId}`);
41+
}
42+
} else if (contentType === "category") {
43+
tagsToRevalidate.push("categories");
44+
if (contentId) {
45+
tagsToRevalidate.push(`category-${contentId}`);
46+
}
47+
} else if (contentType === "tag") {
48+
tagsToRevalidate.push("tags");
49+
if (contentId) {
50+
tagsToRevalidate.push(`tag-${contentId}`);
51+
}
52+
} else if (contentType === "author" || contentType === "user") {
53+
tagsToRevalidate.push("authors");
54+
if (contentId) {
55+
tagsToRevalidate.push(`author-${contentId}`);
56+
}
57+
} else if (contentType === "media") {
58+
tagsToRevalidate.push("media");
59+
if (contentId) {
60+
tagsToRevalidate.push(`media-${contentId}`);
61+
}
62+
}
63+
64+
// Revalidate all determined tags
65+
for (const tag of tagsToRevalidate) {
66+
console.log(`Revalidating tag: ${tag}`);
67+
revalidateTag(tag);
68+
}
69+
70+
return NextResponse.json({
71+
revalidated: true,
72+
message: `Revalidated tags: ${tagsToRevalidate.join(", ")}`,
73+
});
74+
} catch (error) {
75+
console.error("Revalidation error:", error);
76+
return NextResponse.json(
77+
{ message: "Error revalidating content" },
78+
{ status: 500 }
79+
);
80+
}
81+
}

app/layout.tsx

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,23 @@
11
import "./globals.css";
22

3-
import type { Metadata } from "next";
3+
import { Section, Container } from "@/components/craft";
44
import { Inter as FontSans } from "next/font/google";
55
import { ThemeProvider } from "@/components/theme/theme-provider";
6-
import { Button } from "@/components/ui/button";
7-
import { MobileNav } from "@/components/nav/mobile-nav";
86
import { ThemeToggle } from "@/components/theme/theme-toggle";
9-
import { mainMenu, contentMenu } from "@/menu.config";
10-
import { Section, Container } from "@/components/craft";
7+
import { MobileNav } from "@/components/nav/mobile-nav";
118
import { Analytics } from "@vercel/analytics/react";
9+
import { Button } from "@/components/ui/button";
10+
11+
import { mainMenu, contentMenu } from "@/menu.config";
1212
import { siteConfig } from "@/site.config";
13+
import { cn } from "@/lib/utils";
1314

1415
import Balancer from "react-wrap-balancer";
1516
import Logo from "@/public/logo.svg";
1617
import Image from "next/image";
1718
import Link from "next/link";
1819

19-
import { cn } from "@/lib/utils";
20+
import type { Metadata } from "next";
2021

2122
const font = FontSans({
2223
subsets: ["latin"],

package.json

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,40 +10,41 @@
1010
},
1111
"dependencies": {
1212
"@hookform/resolvers": "^3.10.0",
13-
"@radix-ui/react-dialog": "^1.1.7",
14-
"@radix-ui/react-dropdown-menu": "^2.1.7",
15-
"@radix-ui/react-label": "^2.1.3",
16-
"@radix-ui/react-navigation-menu": "^1.2.6",
17-
"@radix-ui/react-scroll-area": "^1.2.4",
18-
"@radix-ui/react-select": "^2.1.7",
19-
"@radix-ui/react-separator": "^1.1.3",
13+
"@radix-ui/react-dialog": "^1.1.11",
14+
"@radix-ui/react-dropdown-menu": "^2.1.12",
15+
"@radix-ui/react-label": "^2.1.4",
16+
"@radix-ui/react-navigation-menu": "^1.2.10",
17+
"@radix-ui/react-scroll-area": "^1.2.6",
18+
"@radix-ui/react-select": "^2.2.2",
19+
"@radix-ui/react-separator": "^1.1.4",
2020
"@radix-ui/react-slot": "^1.2.0",
2121
"@vercel/analytics": "^1.5.0",
2222
"class-variance-authority": "^0.7.1",
2323
"clsx": "^2.1.1",
2424
"lucide-react": "^0.469.0",
25-
"next": "^15.3.0",
25+
"next": "^15.3.1",
2626
"next-themes": "^0.4.6",
27-
"query-string": "^9.1.1",
27+
"query-string": "^9.1.2",
2828
"react": "19.0.0-rc.1",
2929
"react-dom": "19.0.0-rc.1",
30-
"react-hook-form": "^7.55.0",
30+
"react-hook-form": "^7.56.2",
3131
"react-wrap-balancer": "^1.1.1",
3232
"tailwind-merge": "^2.6.0",
3333
"tailwindcss-animate": "^1.0.7",
3434
"use-debounce": "^10.0.4",
35-
"zod": "^3.24.2"
35+
"zod": "^3.24.4"
3636
},
3737
"devDependencies": {
3838
"@tailwindcss/typography": "^0.5.16",
39-
"@types/node": "^20.17.30",
39+
"@types/node": "^20.17.32",
4040
"@types/react": "^18.3.20",
41-
"@types/react-dom": "^18.3.6",
41+
"@types/react-dom": "^18.3.7",
4242
"autoprefixer": "^10.4.21",
43-
"eslint": "^9.24.0",
44-
"eslint-config-next": "^15.3.0",
43+
"eslint": "^9.26.0",
44+
"eslint-config-next": "^15.3.1",
4545
"postcss": "^8.5.3",
4646
"tailwindcss": "^3.4.17",
4747
"typescript": "^5.8.3"
48+
"typescript": "^5.8.3"
4849
}
4950
}

plugin/README.md

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Next.js WordPress Revalidation Plugin
2+
3+
This plugin enables automatic revalidation of your Next.js site when content is changed in WordPress.
4+
5+
## Installation
6+
7+
1. Upload the `next-revalidate.zip` file through the WordPress admin plugin installer, or
8+
2. Extract the `next-revalidate` folder to your `/wp-content/plugins/` directory
9+
3. Activate the plugin through the WordPress admin interface
10+
4. Go to Settings > Next.js Revalidation to configure your settings
11+
12+
## Configuration
13+
14+
### 1. WordPress Plugin Settings
15+
16+
After installing and activating the plugin:
17+
18+
1. Go to Settings > Next.js Revalidation in your WordPress admin
19+
2. Enter your Next.js site URL (without trailing slash)
20+
3. Create a secure webhook secret (a random string), you can use `openssl rand -base64 32` to generate one
21+
4. Save your settings
22+
23+
### 2. Next.js Environment Variables
24+
25+
Add the webhook secret to your Next.js environment variables:
26+
27+
```bash
28+
# .env.local
29+
WORDPRESS_WEBHOOK_SECRET="your-secret-key-here"
30+
```
31+
32+
## How It Works
33+
34+
1. When content in WordPress is created, updated, or deleted, the plugin sends a webhook to your Next.js API route
35+
2. The webhook contains information about the content type (post, page, category, etc.) and ID
36+
3. The Next.js API validates the request using the secret and revalidates the appropriate cache tags
37+
4. Your Next.js site will fetch new content for the affected pages
38+
39+
## Features
40+
41+
- Automatic revalidation for posts, pages, categories, tags, and media
42+
- Manual revalidation option through the admin interface
43+
- Secure webhook communication with your Next.js site
44+
- Optional admin notifications for revalidation events
45+
46+
## Troubleshooting
47+
48+
If revalidation isn't working:
49+
50+
1. Check that your Next.js URL is correct in the plugin settings
51+
2. Verify the webhook secret matches in both WordPress and Next.js
52+
3. Check your server logs for any errors in the API route
53+
4. Enable notifications in the plugin settings to see revalidation status

plugin/next-revalidate.zip

6.34 KB
Binary file not shown.

plugin/next-revalidate/README.txt

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
=== Next.js Revalidation ===
2+
Contributors: 9d8
3+
Tags: next.js, headless, revalidation, cache
4+
Requires at least: 5.0
5+
Tested up to: 6.4
6+
Stable tag: 1.0.1
7+
Requires PHP: 7.2
8+
License: GPLv2 or later
9+
License URI: https://www.gnu.org/licenses/gpl-2.0.html
10+
11+
Automatically revalidate your Next.js site when WordPress content changes.
12+
13+
== Description ==
14+
15+
Next.js Revalidation is a WordPress plugin designed to work with the `next-wp` Next.js starter template. It triggers revalidation of your Next.js site's cache whenever content is added, updated, or deleted in WordPress.
16+
17+
The plugin sends webhooks to your Next.js site's revalidation API endpoint, ensuring your headless frontend always displays the most up-to-date content.
18+
19+
**Key Features:**
20+
21+
* Automatic revalidation when posts, pages, categories, tags, authors, or media are modified
22+
* Settings page to configure your Next.js site URL and webhook secret
23+
* Manual revalidation option for full site refresh
24+
* Support for custom post types and taxonomies
25+
* Optional admin notifications for revalidation events
26+
27+
== Installation ==
28+
29+
1. Upload the `next-revalidate` folder to the `/wp-content/plugins/` directory
30+
2. Activate the plugin through the 'Plugins' menu in WordPress
31+
3. Go to Settings > Next.js Revalidation to configure your Next.js site URL and webhook secret
32+
33+
== Configuration ==
34+
35+
1. Visit Settings > Next.js Revalidation in your WordPress admin
36+
2. Enter your Next.js site URL without a trailing slash (e.g., https://your-site.com)
37+
3. Enter the webhook secret which should match the WORDPRESS_WEBHOOK_SECRET in your Next.js environment
38+
4. Optionally enable admin notifications for revalidation events
39+
5. Click "Save Settings"
40+
41+
== Frequently Asked Questions ==
42+
43+
= What is the webhook secret for? =
44+
45+
The webhook secret provides security for your revalidation API endpoint. It ensures that only your WordPress site can trigger revalidations.
46+
47+
= How do I set up my Next.js site for revalidation? =
48+
49+
Your Next.js site needs an API endpoint at `/api/revalidate` that can process the webhook payloads from this plugin.
50+
See the README in your Next.js project for more details.
51+
52+
= Does this work with custom post types? =
53+
54+
Yes, the plugin automatically detects and handles revalidation for custom post types and taxonomies.
55+
56+
== Changelog ==
57+
58+
= 1.0.1 =
59+
* Fix: Register AJAX actions for manual revalidation
60+
* Fix: Normalize Next.js site URL in settings (remove trailing slash)
61+
= 1.0.0 =
62+
* Initial release
63+
64+
== Upgrade Notice ==
65+
66+
= 1.0.0 =
67+
Initial release of the Next.js Revalidation plugin.

plugin/next-revalidate/index.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?php
2+
// Silence is golden.

0 commit comments

Comments
 (0)