20 minutes. Cinq dossiers. A create-next-app projet tire des messages d’une installation WordPress sur WPGraphQL, l’éditeur possède toujours chaque paragraphe, et next build envoie des pages statiques avec revalidate le nombre que vous voulez. Le passage en dessous des points à woographqldemo.wpengine.com — échange dans votre propre domaine à la fin et les mêmes six étapes s’appliquent.
Ce qu’il y a dans la boîte
@axistaylor/nextpress est un paquet npm et environ 6 000 lignes de TypeScript. Il porte trois morceaux que vous filez dans une application Next.js 16:
withWCR— aNextConfigwrapper qui met en place le domaine WP inverse-proxy et vers l’avant/wp-content,/wp-adminet/graphqlAu prochain tour.<Content>— un composant React qui prend le retour brut HTML WordPress et le rend comme de vrais nœuds React, plus le CSS par bloc dont chacun a besoin.nextImageParser— un analyseur qui échange chaque<img>dans le contenu du WP pournext/imageavecwidth,heightetsizesTiré des attributs du bloc.
Exigence de moteur : WordPress avec WPGraphQL actif. Tout hôte fonctionne — WP Engine, Pantheon, Bluehost, un VPS autogéré — aussi longtemps que /graphql répond.
Étape 1 — Démarrage de l’application suivante
Stock create-next-app avec le modèle TypeScript, puis ajouter @axistaylor/nextpress à partir de npm:
npx create-next-app@latest nextpress-quickstart
cd nextpress-quickstart
npm install @axistaylor/nextpressÉtape 2 — next.config.ts
withWCR prend trois arguments : la base Next config, l’origine WordPress, et l’origine frontend face au public. Le wrapper réécrit chaque URL WordPress absolue que l’éditeur a écrit dans un chemin frontend-relative de sorte que les liens à l’intérieur rendu contenu de poste redescendre sur vos prochaines routes.
import type { NextConfig } from "next";
import { withWCR } from "@axistaylor/nextpress/withWCR";
const wpDomain = "woographqldemo.wpengine.com";
const wpProtocol = "https";
const nextConfig: NextConfig = {
images: {
formats: ["image/avif", "image/webp"],
remotePatterns: [{ protocol: "https", hostname: wpDomain }],
},
env: {
GRAPHQL_ENDPOINT: `${wpProtocol}://${wpDomain}/graphql`,
},
};
export default withWCR(
nextConfig,
{
wpDomain,
wpProtocol,
wpHomeUrl: `${wpProtocol}://${wpDomain}`,
wpSiteUrl: `${wpProtocol}://${wpDomain}`,
},
{
frontendDomain: "localhost:3000",
frontendProtocol: "http",
},
);Étape 3 — src/proxy.ts
Prochain 16 rebaptisé middleware.ts à proxy.ts; le rôle et le comportement des allumeurs n’ont pas changé. Le gestionnaire fait deux choses: /wp-* chemins à travers proxyByWCR donc le navigateur ne touche jamais l’origine WordPress, et x-uri l’en-tête de chaque requête non-proxiée afin que les composants du serveur en aval sachent quel URI demander au moteur pour les scripts et les feuilles de style enqueués:
import { NextResponse, NextRequest } from "next/server";
import { proxyByWCR, isProxiedRoute } from "@axistaylor/nextpress/proxyByWCR";
export const proxy = async (request: NextRequest) => {
const pathname = request.nextUrl.pathname;
if (isProxiedRoute(pathname)) {
return proxyByWCR(request);
}
const headers = new Headers(request.headers);
headers.set("x-uri", pathname);
return NextResponse.next({ request: { headers } });
};
export const config = {
matcher: [
"/atx/:instance/proxiee",
"/atx/:instance/wp",
"/atx/:instance/wc",
"/atx/:instance/wp-internal-assets/:path*",
"/atx/:instance/wp-assets/:path*",
"/atx/:instance/wp-json/:path*",
"/((?!_next|api|favicon.ico|sw.js|.*\\.).*)",
],
};Étape 4 — src/lib/wp.ts
Trois fonctions d’async, une partagée fetch Enveloppe. fetchPosts charge la page d’index, fetchPostBySlug charge la page de publication, fetchPostSlugs lecteurs generateStaticParams. Les next: { revalidate: 60 } indice sur le fetch est le seul contrôle cache dont vous avez besoin — Next gère le reste:
const ENDPOINT = process.env.GRAPHQL_ENDPOINT!;
async function gql<T>(query: string, variables: Record<string, unknown> = {}): Promise<T> {
const res = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, variables }),
next: { revalidate: 60 },
});
if (!res.ok) throw new Error(`WPGraphQL ${res.status}`);
const { data, errors } = await res.json();
if (errors?.length) throw new Error(errors[0].message);
return data as T;
}
export interface PostSummary {
slug: string;
title: string;
date: string;
excerpt: string | null;
featuredImage: { sourceUrl: string; altText: string } | null;
}
export interface Post extends PostSummary {
content: string;
contentCssClasses: string;
}
export async function fetchPosts(first = 20): Promise<PostSummary[]> {
const data = await gql<{ posts: { nodes: PostSummary[] } }>(`
query Posts($first: Int!) {
posts(first: $first, where: { status: PUBLISH, orderby: { field: DATE, order: DESC } }) {
nodes {
slug title date excerpt
featuredImage { node { sourceUrl altText } }
}
}
}`, { first });
return data.posts.nodes;
}
export async function fetchPostSlugs(): Promise<string[]> {
const data = await gql<{ posts: { nodes: { slug: string }[] } }>(`
{ posts(first: 100, where: { status: PUBLISH }) { nodes { slug } } }`);
return data.posts.nodes.map(n => n.slug);
}
export async function fetchPostBySlug(slug: string): Promise<Post | null> {
const data = await gql<{ post: Post | null }>(`
query Post($slug: ID!) {
post(id: $slug, idType: SLUG) {
slug title date excerpt
content(format: RENDERED)
contentCssClasses
featuredImage { node { sourceUrl altText } }
}
}`, { slug });
return data.post;
}Étape 5 — /blog indice
Un composant serveur, un awaitPas d’hameçons. Le tableau de poste est juste JSX:
import Link from "next/link";
import { fetchPosts } from "@/lib/wp";
export const metadata = { title: "Blog" };
export default async function BlogIndex() {
const posts = await fetchPosts(20);
return (
<ul>
{posts.map((post) => (
<li key={post.slug}>
<Link href={`/blog/${post.slug}`}>{post.title}</Link>
<time>{new Date(post.date).toLocaleDateString()}</time>
</li>
))}
</ul>
);
}Étape 6 — /blog/[slug]
generateStaticParams transforme chaque limace publiée en une route de construction-temps. Le rendu est de deux lignes: un titre et un <Content>.
import { notFound } from "next/navigation";
import { Content, nextImageParser } from "@axistaylor/nextpress";
import { fetchPostBySlug, fetchPostSlugs } from "@/lib/wp";
export async function generateStaticParams() {
const slugs = await fetchPostSlugs();
return slugs.map((slug) => ({ slug }));
}
export default async function PostPage({ params }: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const post = await fetchPostBySlug(slug);
if (!post) notFound();
return (
<article>
<h1>{post.title}</h1>
<Content
content={post.content}
contentCssClasses={post.contentCssClasses}
parsers={[nextImageParser()]}
/>
</article>
);
}Cinq fichiers, deux domaines, un npx. L’éditeur ne bouge jamais; le calque de livraison change en dessous.

Quoi ? <Content> fait pour vous
Le corps de poste est une chaîne de bloc WordPress HTML. Le coller dans un dangerouslySetInnerHTML Tu perdrais trois choses dans le processus. <Content> les gère:
- Parcourt le HTML dans un arbre Réagir. Chaque
<p>,<figure>,<blockquote>est un vrai nœud React que vous pouvez surcharger avec un analyseur — voilà commentnextImageParserswaps<img>pournext/imageavec aucun changement de balisage en amont. - Tire le CSS de chaque bloc dans la réponse. Block-supports inline rules, theme.json palette jetons, plugin-injected feuilles — ils expédient en ligne avec la page donc il n’y a pas de changement de la disposition après hydratation.
- Gardez
<script type="application/json">blobs intacts. C’est ainsi que les blocs interactifs (moteurs de mouvement, accordéons, n’importe quoi avec un script de vue) indiquent à la main leur code côté client sans aller-retour API séparé.
Prochaines étapes
Les mêmes six fichiers s’étendent dans quelques directions évidentes. Catégories et balises sont un autre WPGraphQL where argumentation sur fetchPosts. Commentaires et pagination le long de la même requête. Recherche atteint le search et vous expédiez une /search?q=... dans 20 minutes.
Source complète C’est pas vrai.. Dossiers d’emballage à /docs/nextpress. Si vous voulez le pourquoi architectural derrière garder WordPress et ajouter Next.js comme une couche de livraison, cette pièce arrive.

Leave a Reply