npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@siter/headless-gutenberg-react

v1.0.3

Published

React renderer foundation for headless WordPress Gutenberg content.

Readme

@siter/headless-gutenberg-react

Render WordPress Gutenberg content in any React app -- with CSS loading, HTML sanitization, and interactive block hydration.

Works with React, Lovable, Next.js, Vite, and any headless WordPress setup.

Installation

npm install @siter/headless-gutenberg-react

Peer dependencies: react >= 18, react-dom >= 18.

Quick Start

Render a WordPress post by ID

import { WordPressPageRenderer } from '@siter/headless-gutenberg-react';

export function BlogPost() {
  return (
    <WordPressPageRenderer
      wpBaseUrl="https://your-wordpress-site.com"
      id={42}
      showTitle
      loadingFallback={<p>Loading...</p>}
      errorFallback={<p>Failed to load content.</p>}
    />
  );
}

Render by slug

<WordPressPageRenderer
  wpBaseUrl="https://your-wordpress-site.com"
  postType="pages"
  slug="about"
  showTitle
/>

Manual control with hooks

For full control over fetching, CSS injection, and rendering:

import {
  useWordPressContent,
  GutenbergRenderer,
  useHeadlessAssets,
} from '@siter/headless-gutenberg-react';

export function CustomRenderer() {
  const { post, loading, error } = useWordPressContent({
    wpBaseUrl: 'https://your-wordpress-site.com',
    postType: 'pages',
    id: 42,
  });

  useHeadlessAssets(post?.siter_headless?.css_urls);

  if (loading) return <p>Loading...</p>;
  if (error || !post) return <p>Error loading content.</p>;

  return (
    <GutenbergRenderer
      html={post.siter_headless?.rendered_html ?? post.content.rendered}
      assets={post.siter_headless}
    />
  );
}

Layout Customization

WordPress themes define --wp--style--global--content-size and --wp--style--global--wide-size to control content and wide block widths. By default, these values come from the WordPress-generated CSS loaded via siter_headless.css_urls.

To override them for your site (e.g. to match your app's container width):

<GutenbergRenderer
  html={html}
  assets={assets}
  contentSize="720px"
  wideSize="1200px"
/>

Or with the convenience component:

<WordPressPageRenderer
  wpBaseUrl="https://your-wordpress-site.com"
  id={42}
  contentSize="720px"
  wideSize="1200px"
/>

When not provided, the values fall through to the WordPress theme CSS. This lets you use the theme defaults or override per-site.

Interactivity

Interactive Gutenberg blocks work automatically. The package bundles the WordPress Interactivity API runtime and block view scripts locally -- no remote script loading or CORS configuration needed.

| Block | Behavior | Status | |-------|----------|--------| | Accordion | Expand/collapse sections | Working | | Image lightbox | Click image to open fullscreen overlay | Working | | Gallery | Multi-image lightbox with navigation | Working | | Tabs | Tabbed content switching | Bundled | | File | Download file blocks | Bundled |

How it works

  1. GutenbergRenderer renders WordPress HTML via ref-based DOM injection (outside React's virtual DOM).
  2. useInteractiveBlocks detects data-wp-interactive attributes in the rendered DOM.
  3. injectServerData extracts image metadata from the HTML and injects it as initial state for the interactivity runtime.
  4. injectLightboxOverlay creates the lightbox overlay element that WordPress normally renders server-side in wp_footer.
  5. The single interactivity.js bundle loads as an ES module containing the runtime + all block view scripts.
  6. The runtime finds [data-wp-interactive] elements and hydrates them with Preact.

Configuring the interactivity base path

By default, interactivity scripts are loaded from /interactivity/. If you serve them from a different path:

<GutenbergRenderer
  html={html}
  assets={assets}
  interactivityBasePath="/custom/path/to/interactivity/"
/>

WordPress REST API

The package fetches from the WordPress REST API with the ?siter_headless=1 parameter:

GET /wp-json/wp/v2/posts/{id}?siter_headless=1
GET /wp-json/wp/v2/pages?slug={slug}&siter_headless=1

The siter_headless response field provides:

| Field | Type | Description | |-------|------|-------------| | wrapper | string | CSS class selector for the content wrapper | | status | string | ready, dirty, generating, failed, missing | | css_urls | string[] | Generated CSS stylesheet URLs | | blocks | string[] | Gutenberg block types used in the post | | rendered_html | string | Full rendered HTML from WordPress |

CORS Setup

Your WordPress site must send CORS headers for the React app's domain. The Siter plugin handles this. For custom setups:

Access-Control-Allow-Origin: https://your-app-domain.com
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type

API Reference

Components

WordPressPageRenderer

Fetches and renders a WordPress post/page. Handles loading, error states, CSS injection, and interactivity.

| Prop | Type | Default | Description | |------|------|---------|-------------| | wpBaseUrl | string | (required) | WordPress site URL | | postType | string | 'posts' | REST API post type | | id | number | -- | Post ID (provide id or slug) | | slug | string | -- | Post slug (provide id or slug) | | htmlField | 'rendered_html' \| 'content' | 'rendered_html' | Which field to use for HTML | | wrapperClass | string | from assets.wrapper | Override wrapper CSS class | | siteBlocksClass | string | 'wp-site-blocks' | Override inner div class | | contentSize | string | -- | Override --wp--style--global--content-size | | wideSize | string | -- | Override --wp--style--global--wide-size | | interactivityBasePath | string | '/interactivity/' | Path to interactivity bundle | | showTitle | boolean | false | Render post title as <h1> | | loadingFallback | ReactNode | null | Loading state UI | | errorFallback | ReactNode | null | Error state UI | | className | string | -- | Additional CSS class for outer wrapper |

GutenbergRenderer

Renders raw HTML with sanitization, wrapper class, layout overrides, and interactivity hydration.

| Prop | Type | Default | Description | |------|------|---------|-------------| | html | string | (required) | HTML content to render | | assets | SiterHeadlessAssets | -- | Asset metadata from REST response | | className | string | -- | Additional CSS class for outer wrapper | | wrapperClass | string | from assets.wrapper | Override wrapper CSS class | | siteBlocksClass | string | 'wp-site-blocks' | Inner div class | | contentSize | string | -- | Override --wp--style--global--content-size | | wideSize | string | -- | Override --wp--style--global--wide-size | | interactivityBasePath | string | '/interactivity/' | Path to interactivity bundle | | sanitize | boolean | true | Enable HTML sanitization |

Hooks

useWordPressContent(options)

Fetches a WordPress post by ID or slug.

const { post, loading, error, refetch } = useWordPressContent({
  wpBaseUrl: 'https://example.com',
  postType: 'pages',
  id: 42,
});

Returns { post, loading, error, refetch }.

useHeadlessAssets(cssUrls)

Injects CSS <link> tags into the document head. Deduplicates, tracks load/error, cleans up on unmount.

useHeadlessAssets(post?.siter_headless?.css_urls);

Returns { loaded, loading, error }.

useInteractiveBlocks(options)

Loads the interactivity bundle for detected interactive blocks. Called internally by GutenbergRenderer.

Returns { loaded, error }.

Utilities

  • sanitizeGutenbergHtml(html) -- Sanitizes HTML with DOMPurify, preserving data-wp-* attributes.
  • normalizeWrapper(wrapper) -- Converts CSS selector (.my-class) to class name (my-class).

Types

All types are exported for TypeScript consumers:

import type {
  SiterHeadlessAssets,
  WordPressRenderedContent,
  GutenbergRendererProps,
  WordPressPageRendererProps,
  AssetStatus,
} from '@siter/headless-gutenberg-react';

Lovable Integration

This package works in Lovable apps:

import { WordPressPageRenderer } from '@siter/headless-gutenberg-react';

export default function WordPressPage() {
  return (
    <div className="max-w-4xl mx-auto py-8">
      <WordPressPageRenderer
        wpBaseUrl="https://your-wordpress-site.com"
        postType="pages"
        slug="home"
        showTitle
        contentSize="720px"
        loadingFallback={<p className="text-gray-500">Loading...</p>}
        errorFallback={<p className="text-red-500">Could not load page.</p>}
      />
    </div>
  );
}

Security

  • All HTML is sanitized via DOMPurify before rendering. <script> tags and inline event handlers are stripped.
  • data-wp-* attributes are preserved for WordPress Interactivity API compatibility.
  • HTML is injected via ref-based innerHTML (outside React's virtual DOM) to prevent conflicts with interactivity hydration.
  • CSS URLs are loaded only from the WordPress REST response.
  • Interactivity scripts are bundled locally from npm packages -- no remote script loading from WordPress at runtime.

Local Development

npm install
npm run dev       # playground at http://127.0.0.1:5173
npm run check     # typecheck + lint + test + build
npm run test:e2e  # Playwright browser tests

See docs/quickstart.md for daily commands and docs/local-development.md for full setup.

Publishing

Publishing uses GitHub Actions with npm trusted publishing (OIDC). A GitHub release triggers the publish workflow.

Documentation

License

MIT