Skip to content

Commit 3ff04a5

Browse files
committed
docs: fix readme and wp-env json
1 parent 7652d6d commit 3ff04a5

File tree

7 files changed

+453
-191
lines changed

7 files changed

+453
-191
lines changed

examples/next/webhooks-isr/.wp-env.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"phpVersion": "7.4",
33
"plugins": [
44
"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",
56
"../../../plugins/wp-graphql-headless-webhooks"
67
],
78
"config": {
@@ -18,6 +19,6 @@
1819
".htaccess": "./wp-env/setup/.htaccess"
1920
},
2021
"lifecycleScripts": {
21-
"afterStart": "wp-env run cli && wp-env run cli -- wp rewrite structure '/%postname%/' && wp-env run cli -- wp rewrite flush"
22+
"afterStart": "wp-env run cli -- wp rewrite structure '/%postname%/' && wp-env run cli -- wp rewrite flush"
2223
}
2324
}
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: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,32 @@
11
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
22

3-
// Get the WordPress URL from environment variables
4-
// More info: https://nextjs.org/docs/basic-features/environment-variables
53
const WORDPRESS_URL = process.env.NEXT_PUBLIC_WORDPRESS_URL;
64

7-
// Initialize Apollo Client with the link and cache configuration
8-
// More info: https://www.apollographql.com/docs/react/api/core/ApolloClient/
9-
export const client = new ApolloClient({
10-
link: new HttpLink({
11-
uri: WORDPRESS_URL + "/graphql",
12-
useGETForQueries: true,
13-
}),
14-
ssrMode: typeof window === "undefined", // Enable SSR mode for server-side rendering
15-
cache: new InMemoryCache(),
16-
});
5+
function createApolloClient() {
6+
return new ApolloClient({
7+
link: new HttpLink({
8+
uri: WORDPRESS_URL + "/graphql",
9+
useGETForQueries: true,
10+
}),
11+
ssrMode: typeof window === "undefined",
12+
cache: new InMemoryCache(),
13+
});
14+
}
15+
16+
17+
let apolloClient;
18+
19+
export function getApolloClient() {
20+
// On the server, always create a new client
21+
if (typeof window === "undefined") {
22+
return createApolloClient();
23+
}
24+
25+
if (!apolloClient) {
26+
apolloClient = createApolloClient();
27+
}
28+
29+
return apolloClient;
30+
}
31+
32+
export const client = getApolloClient();

examples/next/webhooks-isr/example-app/src/pages/[...uri].js

Lines changed: 29 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import Archive from "@/components/Archive";
22
import CouldNotLoad from "@/components/CouldNotLoad";
33
import Single from "@/components/Single";
4-
import { client } from "@/lib/client";
4+
import { getApolloClient } from "@/lib/client";
55
import { gql } from "@apollo/client";
66

77
const ARCHIVE_TYPES = ["User", "Category", "Tag"];
@@ -111,25 +111,38 @@ const GET_CONTENT = gql`
111111
}
112112
`;
113113

114-
// Next.js function to fetch data on the server side before rendering the page
115-
export async function getServerSideProps({ params }) {
116-
const { data } = await client.query({
117-
query: GET_CONTENT,
118-
variables: {
119-
uri: params.uri.join("/"),
120-
},
121-
});
114+
// Static generation with ISR
115+
export async function getStaticProps({ params }) {
116+
try {
117+
const { data } = await getApolloClient().query({
118+
query: GET_CONTENT,
119+
variables: {
120+
uri: params.uri.join("/"),
121+
},
122+
});
123+
if (!data?.nodeByUri) {
124+
return {
125+
notFound: true,
126+
};
127+
}
122128

123-
// Return 404 page if no data is found
124-
if (!data?.nodeByUri)
129+
console.debug("Fetched data:", data);
130+
return {
131+
props: {
132+
data,
133+
},
134+
revalidate: 60,
135+
};
136+
} catch (error) {
137+
console.error("Error fetching data:", error);
125138
return {
126139
notFound: true,
127140
};
128-
129-
// Pass the fetched data to the page component as props
141+
}
142+
}
143+
export async function getStaticPaths() {
130144
return {
131-
props: {
132-
data,
133-
},
145+
paths: [],
146+
fallback: 'blocking',
134147
};
135148
}

examples/next/webhooks-isr/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"name": "hybrid-sitemap-apollo",
2+
"name": "webhooks-isr-example",
33
"version": "1.0.0",
4-
"description": "A Next.js application that fetches and transforms WordPress sitemaps with clean URL formatting using Apollo Client.",
4+
"description": "A Next.js application demonstrating full Incremental Static Regeneration (ISR) integrated with WordPress webhooks for on-demand page revalidation.",
55
"scripts": {
66
"example:build": "pnpm run wp:images:unzip && pnpm run example:dev:install && pnpm run wp:start && pnpm run wp:db:import && pnpm run example:start",
77
"example:dev:install": "cd example-app && npm install && cd ..",
@@ -16,8 +16,7 @@
1616
"wp:db:export": "wp-env run cli -- wp db export /var/www/html/db/database.sql",
1717
"wp:db:import": "wp-env run cli -- wp db import /var/www/html/db/database.sql",
1818
"wp:images:unzip": "rm -rf wp-env/uploads/ && unzip wp-env/uploads.zip -d wp-env;",
19-
"wp:images:zip": "zip -r wp-env/uploads.zip wp-env/uploads",
20-
"plugin:build": "zip -r hwpt-wpgraphql-sitemap.zip hwpt-wpgraphql-sitemap"
19+
"wp:images:zip": "zip -r wp-env/uploads.zip wp-env/uploads"
2120
},
2221
"keywords": [
2322
"headless",

0 commit comments

Comments
 (0)