Skip to content

Commit 1087e94

Browse files
authored
Merge pull request #245 from wpengine/feat-webhooks-isr-example
feat: first iteration of Webhooks Example project
2 parents 8d4a752 + 3ff04a5 commit 1087e94

36 files changed

+1670
-158
lines changed
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"phpVersion": "7.4",
3+
"plugins": [
4+
"https://github.com/wp-graphql/wp-graphql/releases/latest/download/wp-graphql.zip",
5+
"https://downloads.wordpress.org/plugin/code-snippets.3.6.8.zip",
6+
"../../../plugins/wp-graphql-headless-webhooks"
7+
],
8+
"config": {
9+
"WP_DEBUG": true,
10+
"SCRIPT_DEBUG": false,
11+
"GRAPHQL_DEBUG": true,
12+
"WP_DEBUG_LOG": true,
13+
"WP_DEBUG_DISPLAY": false,
14+
"SAVEQUERIES": false
15+
},
16+
"mappings": {
17+
"db": "./wp-env/db",
18+
"wp-content/uploads": "./wp-env/uploads",
19+
".htaccess": "./wp-env/setup/.htaccess"
20+
},
21+
"lifecycleScripts": {
22+
"afterStart": "wp-env run cli -- wp rewrite structure '/%postname%/' && wp-env run cli -- wp rewrite flush"
23+
}
24+
}
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
# WordPress to Next.js Webhooks Plugin Integration
2+
## Overview
3+
This integration enables seamless communication between a WordPress backend and a Next.js frontend using webhooks. When content updates occur in WordPress, webhooks notify the Next.js application to revalidate and update its cached pages, ensuring fresh and consistent content delivery.
4+
5+
## Features
6+
7+
*Incremental Static Regeneration (ISR) Showcase – Demonstrates Next.js ISR fully working with WordPress-triggered webhooks.
8+
9+
*On-Demand Revalidation – Webhooks notify Next.js to revalidate specific pages when WordPress content changes.
10+
11+
*Relative Path Payloads – Webhook payloads send clean relative paths (e.g., /posts/my-post) for accurate revalidation.
12+
13+
*Secure Webhook Requests – Uses secret tokens in headers to authenticate webhook calls.
14+
15+
*Flexible HTTP Methods & Headers – Supports POST requests with custom headers for integration flexibility.
16+
17+
*WordPress Native Integration – Uses WordPress Custom Post Types and hooks for managing webhooks.
18+
19+
*Extensible & Developer Friendly – Easily customizable payloads and event triggers via WordPress filters and actions.
20+
21+
## Prerequisites
22+
23+
* WordPress site with the wpgraphql-headless-webhooks plugin installed.
24+
* Next.js project (Node.js v18+ recommended).
25+
* Environment variables configured for WordPress URL and webhook secret.
26+
27+
## Setup
28+
### Environment Variables
29+
Create or update your .env.local in your Next.js project:
30+
31+
```ini
32+
NEXT_PUBLIC_WORDPRESS_URL=http://your-wordpress-site.com
33+
WEBHOOK_REVALIDATE_SECRET=your_webhook_secret_token
34+
```
35+
36+
### Creating a Test Webhook in WordPress
37+
Add this PHP snippet to your theme’s `functions.php` or a custom plugin to create a webhook that triggers on post updates and calls your Next.js revalidation API:
38+
39+
```php
40+
function create_test_post_published_webhook() {
41+
// Get the repository instance from your plugin
42+
$repository = \WPGraphQL\Webhooks\Plugin::instance()->get_repository();
43+
44+
// Define webhook properties
45+
$name = 'Test Post Published Webhook';
46+
$event = 'post_updated';
47+
$url = 'http://localhost:3000/api/revalidate'; // Update to your Next.js API URL
48+
$method = 'POST';
49+
50+
$headers = [
51+
'X-Webhook-Secret' => 'your_webhook_secret_token', // Must match Next.js secret
52+
'Content-Type' => 'application/json',
53+
];
54+
$result = $repository->create( $name, $event, $url, $method, $headers );
55+
56+
if ( is_wp_error( $result ) ) {
57+
error_log( 'Failed to create webhook: ' . $result->get_error_message() );
58+
} else {
59+
error_log( 'Webhook created successfully with ID: ' . $result );
60+
}
61+
}
62+
63+
// Run once, for example on admin_init or manually trigger it
64+
add_action( 'admin_init', 'create_test_post_published_webhook' );
65+
```
66+
67+
## Modifying the Webhook Payload to Send Relative Paths
68+
Add this filter to your WordPress plugin or theme to ensure the webhook payload sends a relative path (required by Next.js revalidate API):
69+
70+
```php
71+
add_filter( 'graphql_webhooks_payload', function( array $payload, $webhook ) {
72+
error_log('[Webhook] Initial payload: ' . print_r($payload, true));
73+
if ( ! empty( $payload['post_id'] ) ) {
74+
$post_id = $payload['post_id'];
75+
error_log('[Webhook] Processing post ID: ' . $post_id);
76+
77+
$permalink = get_permalink( $post_id );
78+
79+
if ( $permalink ) {
80+
// Extract relative path from permalink URL
81+
$path = parse_url( $permalink, PHP_URL_PATH );
82+
$payload['path'] = $path;
83+
error_log('[Webhook] Added relative path: ' . $path);
84+
} else {
85+
error_log('[Webhook] Warning: Failed to get permalink for post ID: ' . $post_id);
86+
}
87+
} else {
88+
error_log('[Webhook] Notice: No post_id in payload');
89+
}
90+
91+
// Log final payload state
92+
error_log('[Webhook] Final payload: ' . print_r($payload, true));
93+
94+
return $payload;
95+
}, 10, 2 );
96+
```
97+
98+
99+
100+
## How It Works
101+
This integration:
102+
103+
* When a post is updated in WordPress, the webhook triggers and sends a POST request to the Next.js revalidation API.
104+
* The payload includes a relative path extracted from the post permalink.
105+
* The Next.js API verifies the secret token from the header and calls res.revalidate(path) to refresh the cached page.
106+
* This keeps your frontend content in sync with WordPress backend updates.
107+
108+
# Running the example with wp-env
109+
110+
## Prerequisites
111+
112+
**Note** Please make sure you have all prerequisites installed as mentioned above and Docker running (`docker ps`)
113+
114+
## Setup Repository and Packages
115+
116+
- Clone the repo `git clone https://github.com/wpengine/hwptoolkit.git`
117+
- Install packages `cd hwptoolkit && pnpm install`
118+
- Setup a .env file under `examples/next/webhooks-isr/example-app` with `NEXT_PUBLIC_WORDPRESS_URL=http://localhost:8888`
119+
e.g.
120+
121+
```bash
122+
echo "NEXT_PUBLIC_WORDPRESS_URL=http://localhost:8888" > examples/next/webhooks-isr/example-app/.env
123+
echo "WEBHOOK_REVALIDATE_SECRET=your_webhook_secret_token" > examples/next/webhooks-isr/example-app/.env
124+
```
125+
126+
## Build and start the application
127+
128+
- `cd examples/next/webhooks-isr`
129+
- Then run `pnpm example:build` will build and start your application.
130+
- This does the following:
131+
- Unzips `wp-env/uploads.zip` to `wp-env/uploads` which is mapped to the wp-content/uploads directory for the Docker container.
132+
- Starts up [wp-env](https://developer.wordpress.org/block-editor/getting-started/devenv/get-started-with-wp-env/)
133+
- Imports the database from [wp-env/db/database.sql](wp-env/db/database.sql)
134+
- Install Next.js dependencies for `example-app`
135+
- Runs the Next.js dev script
136+
137+
Congratulations, WordPress should now be fully set up.
138+
139+
| Frontend | Admin |
140+
|----------|------------------------------|
141+
| [http://localhost:3000/](http://localhost:3000/) | [http://localhost:8888/wp-admin/](http://localhost:8888/wp-admin/) |
142+
143+
144+
> **Note:** The login details for the admin is username "admin" and password "password"
145+
146+
147+
## Command Reference
148+
149+
| Command | Description |
150+
|------------------------|-----------------------------------------------------------------------------|
151+
| `example:build` | Prepares the environment by unzipping images, starting WordPress, importing the database, and starting the application. |
152+
| `example:dev` | Runs the Next.js development server. |
153+
| `example:dev:install` | Installs the required Next.js packages. |
154+
| `example:start` | Starts WordPress and the Next.js development server. |
155+
| `example:stop` | Stops the WordPress environment. |
156+
| `example:prune` | Rebuilds and restarts the application by destroying and recreating the WordPress environment. |
157+
| `wp:start` | Starts the WordPress environment. |
158+
| `wp:stop` | Stops the WordPress environment. |
159+
| `wp:destroy` | Completely removes the WordPress environment. |
160+
| `wp:db:query` | Executes a database query within the WordPress environment. |
161+
| `wp:db:export` | Exports the WordPress database to `wp-env/db/database.sql`. |
162+
| `wp:db:import` | Imports the WordPress database from `wp-env/db/database.sql`. |
163+
| `wp:images:unzip` | Extracts the WordPress uploads directory. |
164+
| `wp:images:zip` | Compresses the WordPress uploads directory. |
165+
166+
>**Note** You can run `pnpm wp-env` and use any other wp-env command. You can also see <https://www.npmjs.com/package/@wordpress/env> for more details on how to use or configure `wp-env`.
167+
168+
### Database access
169+
170+
If you need database access add the following to your wp-env `"phpmyadminPort": 11111,` (where port 11111 is not allocated).
171+
172+
You can check if a port is free by running `lsof -i :11111`
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.*
7+
.yarn/*
8+
!.yarn/patches
9+
!.yarn/plugins
10+
!.yarn/releases
11+
!.yarn/versions
12+
13+
# testing
14+
/coverage
15+
16+
# next.js
17+
/.next/
18+
/out/
19+
20+
# production
21+
/build
22+
23+
# misc
24+
.DS_Store
25+
*.pem
26+
27+
# debug
28+
npm-debug.log*
29+
yarn-debug.log*
30+
yarn-error.log*
31+
.pnpm-debug.log*
32+
33+
# env files (can opt-in for committing if needed)
34+
.env*
35+
36+
# vercel
37+
.vercel
38+
39+
# typescript
40+
*.tsbuildinfo
41+
next-env.d.ts
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/pages/api-reference/create-next-app).
2+
3+
## Getting Started
4+
5+
First, run the development server:
6+
7+
```bash
8+
npm run dev
9+
# or
10+
yarn dev
11+
# or
12+
pnpm dev
13+
# or
14+
bun dev
15+
```
16+
17+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18+
19+
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
20+
21+
[API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`.
22+
23+
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) instead of React pages.
24+
25+
This project uses [`next/font`](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
26+
27+
## Learn More
28+
29+
To learn more about Next.js, take a look at the following resources:
30+
31+
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
32+
- [Learn Next.js](https://nextjs.org/learn-pages-router) - an interactive Next.js tutorial.
33+
34+
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
35+
36+
## Deploy on Vercel
37+
38+
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
39+
40+
Check out our [Next.js deployment documentation](https://nextjs.org/docs/pages/building-your-application/deploying) for more details.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { dirname } from "path";
2+
import { fileURLToPath } from "url";
3+
import { FlatCompat } from "@eslint/eslintrc";
4+
5+
const __filename = fileURLToPath(import.meta.url);
6+
const __dirname = dirname(__filename);
7+
8+
const compat = new FlatCompat({
9+
baseDirectory: __dirname,
10+
});
11+
12+
const eslintConfig = [...compat.extends("next/core-web-vitals")];
13+
14+
export default eslintConfig;
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"compilerOptions": {
3+
"paths": {
4+
"@/*": ["./src/*"]
5+
}
6+
}
7+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/** @type {import('next').NextConfig} */
2+
const nextConfig = {
3+
reactStrictMode: true,
4+
};
5+
6+
export default nextConfig;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "example-app",
3+
"version": "0.1.0",
4+
"private": true,
5+
"scripts": {
6+
"dev": "next dev --turbopack",
7+
"build": "next build",
8+
"start": "next start",
9+
"lint": "next lint"
10+
},
11+
"dependencies": {
12+
"@apollo/client": "^3.13.8",
13+
"fast-xml-parser": "^5.1.0",
14+
"graphql": "^16.10.0",
15+
"next": "15.3.3",
16+
"react": "^19.0.0",
17+
"react-dom": "^19.0.0"
18+
},
19+
"devDependencies": {
20+
"@eslint/eslintrc": "^3",
21+
"@tailwindcss/postcss": "^4",
22+
"@types/react": "19.1.7",
23+
"eslint": "^9",
24+
"eslint-config-next": "15.2.4",
25+
"tailwindcss": "^4"
26+
}
27+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const config = {
2+
plugins: ["@tailwindcss/postcss"],
3+
};
4+
5+
export default config;
25.3 KB
Binary file not shown.

0 commit comments

Comments
 (0)