Skip to content

Commit 3c09485

Browse files
committed
add wpgraphql tutorial
1 parent c57605b commit 3c09485

File tree

5 files changed

+349
-0
lines changed

5 files changed

+349
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: "Implement Previews with REST API"
3+
description: "Learn how to implement preview functionality using the HWP Previews plugin in your headless WordPress application with REST API."
4+
---
5+
6+
@TODO
165 KB
Loading
166 KB
Loading
Lines changed: 343 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,343 @@
1+
---
2+
title: "Build Previews with Next.js and WPGraphQL"
3+
description: "Learn how to build a Next.js application with WordPress preview functionality using WPGraphQL and the HWP Previews plugin."
4+
---
5+
6+
## Overview
7+
8+
In this tutorial, we will build a Next.js application that displays WordPress content and enables preview functionality for draft posts. By the end, you will have a working headless WordPress setup where clicking "Preview" in WordPress opens your draft content in Next.js.
9+
10+
We will use Next.js Draft Mode, WPGraphQL for data fetching, and WordPress Application Passwords for authentication.
11+
12+
> [!TIP]
13+
> You can see the completed project in the [hwp-preview-wpgraphql example](https://github.com/wpengine/hwptoolkit/tree/main/plugins/hwp-previews/examples/hwp-preview-wpgraphql).
14+
15+
## What you'll build
16+
17+
By following this tutorial, you will create:
18+
19+
* A Next.js application that fetches WordPress content via GraphQL
20+
* An API route that enables Next.js Draft Mode for previews
21+
* Preview functionality that shows draft content when you click "Preview" in WordPress
22+
* Authentication using WordPress Application Passwords
23+
24+
25+
## Prerequisites
26+
27+
Before starting, make sure you have:
28+
29+
* Node.js 18 or higher installed
30+
* A WordPress site with HWP Previews and WPGraphQL plugins installed
31+
* Basic familiarity with Next.js and React
32+
33+
## Step 1: Create the Next.js application
34+
35+
First, we will create a new Next.js project.
36+
37+
Open your terminal and run:
38+
39+
```bash
40+
npx create-next-app@latest my-wordpress-preview
41+
```
42+
43+
When prompted, select:
44+
* TypeScript: No
45+
* ESLint: Yes
46+
* Tailwind CSS: Yes (optional)
47+
* App Router: No (we'll use Pages Router)
48+
49+
Navigate into your project:
50+
51+
```bash
52+
cd my-wordpress-preview
53+
```
54+
55+
You should now see a basic Next.js project structure with a `pages` directory.
56+
57+
## Step 2: Install Apollo Client
58+
59+
We will use Apollo Client to fetch data from WordPress via GraphQL.
60+
61+
Install the required packages:
62+
63+
```bash
64+
npm install @apollo/client graphql
65+
```
66+
67+
Notice that your `package.json` now includes these new dependencies.
68+
69+
## Step 3: Set up Apollo Client
70+
71+
Now we will create an Apollo Client instance to connect to WordPress.
72+
73+
Create a new file `src/lib/client.js`:
74+
75+
```javascript
76+
import { ApolloClient, HttpLink, InMemoryCache } from "@apollo/client";
77+
78+
const WORDPRESS_URL = process.env.NEXT_PUBLIC_WORDPRESS_URL;
79+
80+
export const client = new ApolloClient({
81+
link: new HttpLink({
82+
uri: WORDPRESS_URL + "/graphql",
83+
}),
84+
ssrMode: typeof window === "undefined",
85+
cache: new InMemoryCache(),
86+
});
87+
```
88+
89+
This creates a client that points to your WordPress GraphQL endpoint. Notice how we use an environment variable for the WordPress URL.
90+
91+
## Step 4: Create environment variables
92+
93+
Create a `.env.local` file in your project root:
94+
95+
```bash
96+
NEXT_PUBLIC_WORDPRESS_URL=http://your-wordpress-site.com
97+
98+
WP_USERNAME=admin # WordPress username which you created Application Password for
99+
WP_APP_PASSWORD=**** # WordPress Application Password
100+
WP_PREVIEW_SECRET=**** # Any strong secret string
101+
```
102+
103+
Use your actual WordPress URL and username here. We will cover the Application Password and the secret in a later step.
104+
105+
## Step 5: Create the authentication utility
106+
107+
We need a way to send WordPress credentials with our preview requests.
108+
109+
Create `src/utils/getAuthString.js`:
110+
111+
```javascript
112+
export function getAuthString() {
113+
const username = process.env.WP_USERNAME;
114+
const password = process.env.WP_APP_PASSWORD;
115+
116+
if (!username || !password) {
117+
return null;
118+
}
119+
120+
return "Basic " + Buffer.from(`${username}:${password}`).toString("base64");
121+
}
122+
```
123+
124+
This function creates a Base64-encoded authentication string from your WordPress credentials. You will use this when fetching preview content.
125+
126+
## Step 6: Create the preview API route
127+
128+
Now we will create the API route that enables Draft Mode when WordPress redirects to your preview.
129+
130+
Create `src/pages/api/preview.js`:
131+
132+
```javascript
133+
import { client } from "@/lib/client";
134+
import { getAuthString } from "@/utils/getAuthString";
135+
import { gql } from "@apollo/client";
136+
137+
// GraphQL query to verify the content exists and get its database ID
138+
const GET_CONTENT = gql`
139+
query GetNode($id: ID!) {
140+
contentNode(id: $id, idType: DATABASE_ID, asPreview: true) {
141+
databaseId
142+
}
143+
}
144+
`;
145+
146+
export default async function handler(req, res) {
147+
const { secret, id } = req.query;
148+
149+
if (!id) {
150+
return res.status(400).json({ message: "No ID provided." });
151+
}
152+
153+
// Verify the secret token matches our environment variable for security
154+
if (secret !== process.env.WP_PREVIEW_SECRET) {
155+
return res.status(401).json({ message: "Invalid secret token." });
156+
}
157+
158+
// Query WordPress to verify the content exists and we can access it
159+
const { data } = await client.query({
160+
query: GET_CONTENT,
161+
variables: { id },
162+
context: {
163+
headers: {
164+
Authorization: getAuthString(),
165+
},
166+
},
167+
});
168+
169+
if (!data?.contentNode) {
170+
return res.status(404).json({ message: "Content not found." });
171+
}
172+
173+
// Enable Next.js Draft Mode for this session
174+
res.setDraftMode({ enable: true });
175+
// Redirect to the content page using the database ID
176+
res.redirect("/" + data.contentNode.databaseId);
177+
}
178+
```
179+
180+
This route does three important things:
181+
182+
1. Checks if the secret token matches (security)
183+
2. Verifies the content exists using GraphQL
184+
3. Enables Draft Mode and redirects to the content
185+
186+
## Step 7: Create the content display page
187+
188+
We will create a dynamic page that displays both published and preview content.
189+
190+
Create `src/pages/[identifier].js`:
191+
192+
```javascript
193+
import { client } from "@/lib/client";
194+
import { getAuthString } from "@/utils/getAuthString";
195+
import { gql } from "@apollo/client";
196+
197+
// This query handles both published content and preview content
198+
// It uses GraphQL directives to conditionally fetch from different fields
199+
const GET_CONTENT = gql`
200+
query GetSeedNode($id: ID! = 0, $uri: String! = "", $asPreview: Boolean = false) {
201+
nodeByUri(uri: $uri) @skip(if: $asPreview) {
202+
__typename
203+
... on Post {
204+
title
205+
content
206+
date
207+
}
208+
}
209+
210+
contentNode(id: $id, idType: DATABASE_ID, asPreview: true) @include(if: $asPreview) {
211+
__typename
212+
... on Post {
213+
title
214+
content
215+
date
216+
}
217+
}
218+
}
219+
`;
220+
221+
export default function Content({ data }) {
222+
if (!data) {
223+
return <div>Content not found</div>;
224+
}
225+
226+
return (
227+
<article>
228+
<h1>{data.title}</h1>
229+
<div dangerouslySetInnerHTML={{ __html: data.content }} />
230+
</article>
231+
);
232+
}
233+
234+
export async function getStaticProps({ params, draftMode }) {
235+
// Use different variables based on whether we're in draft mode
236+
const variables = draftMode
237+
? { id: params.identifier, asPreview: true }
238+
: { uri: params.identifier };
239+
240+
// Only send auth headers for preview requests
241+
const headers = draftMode ? { Authorization: getAuthString() } : null;
242+
243+
const { data } = await client.query({
244+
query: GET_CONTENT,
245+
variables,
246+
context: { headers },
247+
});
248+
249+
// Extract content from the appropriate field
250+
const content = draftMode ? data?.contentNode : data?.nodeByUri;
251+
252+
return {
253+
props: { data: content },
254+
revalidate: 60,
255+
};
256+
}
257+
258+
export async function getStaticPaths() {
259+
return {
260+
paths: [],
261+
fallback: "blocking",
262+
};
263+
}
264+
```
265+
266+
Notice how this page handles both preview mode (using `contentNode`) and normal mode (using `nodeByUri`). When Draft Mode is enabled, it sends authentication headers.
267+
268+
## Step 8: Generate a WordPress Application Password
269+
270+
Now we need to create an Application Password in WordPress for authentication.
271+
272+
1. Log into your WordPress admin
273+
2. Go to Users > Profile
274+
3. Scroll down to "Application Passwords"
275+
4. Enter a name like "Next.js Preview"
276+
5. Click "Add Application Password"
277+
278+
![WordPress Application Passwords section showing the form to generate a new application password with a name field and "Add Application Password" button](generate-application-password.png)
279+
280+
Copy the generated password (it will look like `xxxx xxxx xxxx xxxx xxxx xxxx`). You will not be able to see it again.
281+
282+
Update your `.env.local` file with this password:
283+
284+
```bash
285+
WP_APP_PASSWORD=xxxx xxxx xxxx xxxx xxxx xxxx
286+
```
287+
288+
## Step 9: Configure HWP Previews in WordPress
289+
290+
We will now configure the preview URL in WordPress to point to your Next.js app.
291+
292+
1. In WordPress admin, go to Settings > HWP Previews
293+
2. Click the "Posts" tab
294+
3. Check "Enable HWP Previews"
295+
4. In the Preview URL Template field, enter:
296+
```
297+
http://localhost:3000/api/preview?id={ID}&secret=YOUR_SECRET_TOKEN
298+
```
299+
5. Replace `YOUR_SECRET_TOKEN` with a random string (like `my-secret-preview-token-123`)
300+
6. Click "Save Changes"
301+
302+
![WordPress HWP Previews settings page showing the Posts tab with "Enable HWP Previews" checkbox checked and a Preview URL Template field containing the localhost preview URL](configure-hwp-previews.png)
303+
304+
Update your `.env.local` file with the same secret token:
305+
306+
```bash
307+
WP_PREVIEW_SECRET=my-secret-preview-token-123
308+
```
309+
310+
## Step 10: Start your application
311+
312+
Start the Next.js development server:
313+
314+
```bash
315+
npm run dev
316+
```
317+
318+
You should see output indicating the server is running at `http://localhost:3000`.
319+
320+
## Step 11: Test the preview
321+
322+
Now we will test that previews work correctly.
323+
324+
1. In WordPress, create or edit a post
325+
2. Make some changes but do not publish
326+
3. Click the "Preview" button
327+
328+
You should be redirected to your Next.js application showing your draft content. Notice the URL includes your post ID.
329+
330+
![Screenshot showing a Next.js application displaying WordPress draft content in preview mode, with the post title and content visible on the page](preview-view.png)
331+
332+
If you see your draft content, congratulations! Your preview system is working.
333+
334+
## Next steps
335+
336+
Now that you have a working preview system, you can:
337+
338+
* Add support for Pages and custom post types
339+
* Implement a "Disable Preview" button
340+
* Add loading states and error handling
341+
* Deploy your application to production
342+
343+
For more details about extending this setup, see the [complete example](https://github.com/wpengine/hwptoolkit/tree/main/plugins/hwp-previews/examples/hwp-preview-wpgraphql) which includes these additional features.
48.9 KB
Loading

0 commit comments

Comments
 (0)