Plugin + JS packageServer-rendered Gutenberg, no escape hatches
Render WordPress Gutenberg in Next.js — 1:1
NextPress is a WPGraphQL extension plus a Next.js package that brings full Gutenberg fidelity to your headless app. theme.json typography, layered stylesheets, block-supports CSS, the Interactivity API — all rendered server-side.
$composer require axistaylor/nextpress-wordpress
$npm install @axistaylor/nextpress
// what NextPress does
Full Gutenberg, no compromises.
Theme.json, asset dependencies, block-supports CSS, the Interactivity API — NextPress handles the whole stack so your headless pages match the editor preview exactly.
Server-rendered Gutenberg
Async server component. WordPress’s block HTML lands in your Next.js tree at request time with no client hydration tax.
Theme.json fidelity
Palettes, font sizes, spacing, fluid typography — the same tokens the editor uses, threaded through your headless page as CSS variables.
Style isolation
@scope ([data-rendered]) keeps WP’s reset and block styles inside the rendered area, so your app shell stays untouched.
Interactivity API
Script modules + import map for type="module" Gutenberg interactivity blocks — cart drawers, navigation, accordions all work natively.
// four steps
Wire it in, render the page.
— 01 / Install
Install the plugin
Activates templateByUri, globalStyles, and the asset-bridge queries on your WPGraphQL endpoint.
wp plugin install axistaylor-nextpress --activate
# ✓ NextPress activated · GraphQL extensions registered— 02 / Proxy
Configure the proxy
Forward proxied WP asset URLs through Next.js and thread the cart session into rendered pages.
import { NextRequest, NextResponse } from 'next/server'
import { proxyByWCR, isProxiedRoute } from '@axistaylor/nextpress/proxyByWCR'
export const proxy = async (request: NextRequest) => {
const { pathname } = request.nextUrl
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/wp-assets/:path*',
'/atx/:instance/wp-internal-assets/:path*',
'/atx/:instance/wp-json/:path*',
'/atx/:instance/wp',
'/atx/:instance/wc',
'/((?!_next|api|favicon.ico|.*\\.).*)',
],
}— 03 / Wire
Wire the layout
Drop the head and footer slots into your root layout. NextPress emits the stylesheets, scripts, and import map.
import { WPHead, WPFooter } from '@axistaylor/nextpress'
import { fetchAssets, fetchGlobalStyles } from '@/lib/utils'
import { headers } from 'next/headers'
export default async function RootLayout({ children }) {
const uri = (await headers()).get('x-uri') ?? '/'
const [{ stylesheets, scripts, importMap }, globalStyles] =
await Promise.all([fetchAssets(uri), fetchGlobalStyles()])
return (
<html>
<head>
<WPHead
scripts={scripts}
stylesheets={stylesheets}
globalStyles={globalStyles}
importMap={importMap}
pathname={uri}
criticalHandles={['theme', 'wp-block-library']}
/>
</head>
<body>
{children}
<WPFooter scripts={scripts} pathname={uri} />
</body>
</html>
)
}— 04 / Render
Render the page
Your [...uri] catch-all becomes a one-liner. NextPress resolves the template, fetches the block tree, returns the matching React.
import { Content, nextImageParser } from '@axistaylor/nextpress'
import { fetchContentByUri } from '@/lib/utils'
export default async function CatchAllPage({
params,
}: {
params: Promise<{ uri?: string[] }>
}) {
const { uri } = await params
const path = '/' + (uri ?? []).join('/')
const data = await fetchContentByUri(path)
if (!data) return null
return (
<Content
content={data.content}
contentCssClasses={data.contentCssClasses}
parsers={[nextImageParser]}
/>
)
}// coverage
If WordPress can render it, NextPress can too.
NextPress doesn’t care about block names. It walks the block tree, applies the same supports CSS WordPress would, and emits matching React. Custom blocks work out of the box.
theme.json palettesglobalStyles tokenscore blocksblock-supports CSSscript modulesInteractivity APIcustom blocksclassic-editor HTMLreusable blockstemplate partsblock patternsasset dependency order
// with WooGraphQL
Bring the WooCommerce blocks with you.
NextPress renders any Gutenberg block, including the WooCommerce ones. Paired with WooGraphQL, the headless session threads into the rendered block context — so the WooCommerce Cart, Checkout, My Account, and product blocks read the same session your Next.js app does. No double sign-in, no proxy hacks.
- WC Cart / Checkout / My Account blocks render against the user’s real session.
- Cart token shared between the Next app and the rendered block via
@woographql/session-utils. - Product blocks, filters, mini-cart — same WPGraphQL endpoint, no parallel REST surface.
- Block-level mutations (add to cart, apply coupon) round-trip through WooGraphQL.
import { Content, nextImageParser } from '@axistaylor/nextpress'
import { fetchContentByUri } from '@/lib/utils'
import { NextPressLink } from '@/components/NextPressLink'
// proxyByWCR (NextPress middleware) forwards the cart-token
// cookie to WordPress for matching URIs, so the WP renderer
// reads the same session as your Next app. WC Cart, Checkout,
// My Account, and product blocks all render against the user's
// real cart — no withSession wrapper required.
export default async function Page({
params,
}: {
params: Promise<{ uri?: string[] }>
}) {
const { uri } = await params
const path = '/' + (uri ?? []).join('/')
const { content, contentCssClasses } = await fetchContentByUri(path)
return (
<Content
content={content}
contentCssClasses={contentCssClasses}
instance="shop"
linksAs={NextPressLink}
parsers={[nextImageParser]}
/>
)
}Build with NextPress
Installation, configuration, the asset-bridging API, and the full catalogue of blocks NextPress understands — all in the docs.