Rendering Blocks
Introduction
There are multiple approaches to block rendering
Naive Approach
Simply output the generated markup "as is", using dangerouslySetInnerHTML
or the safer SafeHtml
component:
// Using dangerouslySetInnerHTML (not recommended)
<div dangerouslySetInnerHTML={{ __html: content }} />
// Using SafeHtml (recommended)
import { SafeHtml } from '@headstartwp/core/react';
<SafeHtml html={content} />
Blocks as JSON Approach
With this approach, there's some custom PHP logic that parses blocks on the PHP side of things and converts them to JSON to be included in the REST API (or GraphQL).
This approach makes it very easy (and portable) to render blocks anywhere, but on the other hand, it forces the implementation of all supported blocks. Handling server-rendered blocks is also a problem.
In this approach, blocks are parsed on the Backend.
Blocks as Markup Approach
Instead of simply outputting the generated markup "as is", it runs the block markup through an HTML parser (after sanitizing) and selectively (and progressively) replaces block markup with actual React components as needed.
This approach is the simplest way to render blocks as not all blocks need to be actual React components (e.g paragraphs, lists, etc). You only need to create React Components for very specific blocks such as Links, Images, and "dynamic blocks" such as accordion, etc. You can still ignore certain blocks if you want better control of supported blocks (both on the WP side and the front-end side).
In this approach, blocks are parsed on the Frontend.
Which approach should you use?
The framework supports all of these approaches. However, it does not ship with a "blocks-to-rest" plugin. But nothing is stopping you from shipping your own.
Instead, it provides a React API for selectively choosing blocks to be replaced with React components and as we'll show in this article this approach works well for most content-focused websites and can even be implemented in React Native apps to power App screens fully curated by Gutenberg.
The BlocksRenderer component
The BlocksRenderer
component is the heart of the "HTML to React" approach. It receives arbitrary HTML markup, runs its content through wpKsesPost, and replaces markup with react component based on the child components passed to it. This approach is not tied to Gutenberg markup in any way, it can be used as a generic HTML-to-React conversion tool. Here's a simple example of how it works
const MyLinkBlock = ({ domNode, children }) => {
// get the html attributes from the dom node
const { href, rel } = domNode.attribs;
return (
<MyFrameWorkSpecificLinkComponent href={href} rel={rel}>
{children}
</MyFrameWorkSpecificLinkComponent>
);
};
export const Blocks = ({ html }) => {
return (
<BlocksRenderer html={html}>
<MyLinkBlock tagName="a" classList="my-special-anchor" />
</BlocksRenderer>
);
};
In the example above, we're passing the HTML content directly to BlocksRenderer and we're specifying that any anchor tags with a class of "my-special-anchor" should be replaced with MyLinkBlock.
The MyLinkBlock then gets one special prop called domNode which is the DOM Node that's being replaced with a React component. You can use domNode to access attributes of the node such as href and rel in this case.
Alternatively, you can also specify a test function to match dom nodes, the example above could also have been written as shown below
<MyLinkBlock
test={
(node) => node.type === 'tag'
&& node.name === 'a'
&& node.attribs.class === 'my-special-anchor'
}
/>
There are a number of utility functions that make matching nodes and blocks specifically very easy as we'll see now.
Matching blocks with isBlock
The isBlock function matches a DOM node by its tagName and optionally CSS classes.
<BlocksRenderer html={html}>
<MyCustomBlock
test={(node) => isBlock(node, { tagName: 'div', classList: ['block-class-name'] })}
/>
</BlocksRenderer>
Matching Blocks with isBlockByName
The isBlockByName matches a DOM node by its Gutenberg block name. This function requires the HeadstartWP plugin. The plugin appends two special attributes to every block: data-wp-block-name
and data-wp-block-attrs
.
This is a very handy function as it makes it very easy to match any registered Gutenberg block.
<BlocksRenderer html={html}>
<MyCustomParagraphBlock
test={(node) => isBlock(node, 'core/paragraph')}
/>
</BlocksRenderer>
Core Blocks
HeadstartWP takes a distinctive approach to rendering core blocks by rendering them as markup "as-is" without replacing them with custom React components. This design philosophy ensures that core blocks maintain their intended functionality and styling while providing optimal performance.
Why Render Core Blocks As-Is?
The framework's approach to core blocks is intentionally conservative. Rather than replacing every core block with a custom React component, HeadstartWP preserves the original markup generated by WordPress. This approach offers several advantages:
- Performance: No unnecessary JavaScript overhead for simple content blocks
- Consistency: Core blocks maintain their intended behavior and styling
- Reliability: Reduced risk of breaking changes when WordPress updates core blocks
- Maintenance: Less code to maintain and fewer custom components to update
When to Consider Custom Components
While HeadstartWP renders most core blocks as markup, there are specific cases where custom React components are beneficial:
Recommended for customization:
- Links - For custom routing, tracking, or styling
- Images - For performance optimization, lazy loading, or responsive behavior
Consider the impact before replacing:
- Paragraphs - Usually better left as markup
- Headings - Rarely need custom components
- Lists - Simple markup is often sufficient
- Quotes - Custom styling can often be achieved with CSS
Block Styles and Styling
HeadstartWP provides mechanisms to fully load the block styles generated by the block-editor into your Next.js application. This ensures that core blocks maintain their visual appearance without requiring custom React components.
The framework handles:
- Core block styles from WordPress
- Theme-specific block styles
- Custom block editor styles
- Responsive design considerations
Alternative: Custom Blocks
If you find yourself needing to heavily customize core blocks, consider creating a custom block instead. This approach offers several benefits:
- Separation of concerns: Keep core blocks intact while adding custom functionality
- Future-proof: Your customizations won't be affected by WordPress core updates
- Content editor experience: Provide a tailored editing experience for your specific needs
- Maintainability: Easier to maintain and update custom functionality
Best Practices
- Start with core blocks as-is - Most content blocks work perfectly without customization
- Use CSS for styling - Many visual customizations can be achieved with CSS alone
- Custom components for interactivity - Only replace blocks that need custom JavaScript behavior
- Test thoroughly - When you do replace core blocks, ensure all functionality is preserved
- Consider performance - Each custom component adds JavaScript overhead
By following this approach, you can build performant headless WordPress sites that leverage the full power of Gutenberg while maintaining the flexibility to customize where truly needed.
Loading Block Styles
WordPress handles block styles in multiple ways, and HeadstartWP provides robust mechanisms to ensure all block styles are properly loaded in your headless application. Understanding how WordPress manages CSS is crucial for maintaining visual consistency between your WordPress backend and Next.js frontend.
How WordPress Handles Block CSS
WordPress manages block styles through several mechanisms:
- Global CSS: Some CSS is globally loaded by WordPress (e.g.,
block-library.css
) - Conditional CSS: Some CSS is conditionally loaded only when specific blocks are present on the page
- Dynamic CSS: Some CSS is dynamically generated based on the blocks and their configurations on each page
Without proper handling, headless applications would miss these styles, leading to improperly styled blocks.
The BlockLibraryStyles Component
HeadstartWP provides the BlockLibraryStyles
component to load WordPress's global block library CSS into your Next.js application. This component fetches the block-library.css
file from WordPress and injects it inline into your pages.
import { BlockLibraryStyles } from '@headstartwp/next/rsc';
export default async function Layout({ children, params }) {
return (
<html>
<head>
{/* Passing params is only required when using multisite or polylang */}
<BlockLibraryStyles params={await params} />
</head>
<body>
{children}
</body>
</html>
);
}
How BlockLibraryStyles Works
The BlockLibraryStyles
component:
- Fetches CSS from WordPress: Makes a server-side request to WordPress to get the latest
block-library.css
- Caches the result: Caches the CSS to avoid repeated requests
- Inlines the styles: Injects the CSS directly into the HTML document as inline styles
- Handles updates: When you redeploy your application, the cache is refreshed with the latest styles, the cache is always refreshed every 24h.
This approach ensures that:
- CSS is bundled with your application (no external requests from the browser)
- Styles are always up-to-date with your WordPress version
- Performance is optimized through caching
Dynamic Block Styles with post.content.block_styles
For page-specific and dynamically generated CSS, HeadstartWP extends the WordPress REST API to include a block_styles
property in the content object. This property contains all the CSS required for the specific blocks on that page.
import { BlocksRenderer } from '@headstartwp/core/react';
export default function PostContent({ post }) {
const { content } = post;
return (
<article>
{/* Render the blocks with dynamic styles */}
<BlocksRenderer
html={content.rendered}
blockStyles={content.block_styles}
>
{/* Your custom block components */}
</BlocksRenderer>
</article>
);
}
What's Included in block_styles
The block_styles
property includes:
- Global stylesheet: WordPress's global stylesheet (
wp_get_global_stylesheet()
) - Core block supports: CSS for core block features and supports
- Block-specific styles: CSS for each block type present on the page
- Theme styles: Any theme-specific block styles
- Custom block styles: Styles from custom blocks
How it works
The WordPress integration is handled by the Gutenberg.php
class in the HeadstartWP plugin. This class automatically:
- Extends the REST API: Adds
block_styles
property to all public post types - Collects block styles: Gathers WordPress global styles, core block supports, and individual block styles
- Optimizes performance: Only processes block styles when specifically requested (by slug or id)
- Provides customization: Offers multiple filters to control the behavior
The integration works seamlessly without any additional configuration, but you can customize its behavior using the available filters.
For more details on the available filters to customize block styles processing, see the Core Block CSS Loading Filters section.
Performance Considerations
The block styles system is designed for optimal performance:
- Caching: Both the
BlockLibraryStyles
component and the backend PHP code implement caching - Conditional loading: Block styles are only generated when specifically requested
- Inline delivery: CSS is delivered inline, reducing HTTP requests
- Deduplication: The system prevents duplicate styles from being included
Best Practices for Block Styles
- Use BlockLibraryStyles globally: Include it in your layout component to ensure global styles are always available
- Include block_styles conditionally: Only include page-specific styles when rendering block content
- Cache appropriately: Leverage the built-in caching mechanisms
- Test thoroughly: Ensure all block styles render correctly in your headless application
- Monitor performance: Keep an eye on CSS bundle sizes, especially for content-heavy sites
By leveraging these CSS loading mechanisms, your headless WordPress application will maintain perfect visual consistency with your WordPress backend while providing optimal performance for your users.