WordPress server-side rendering can sound like a modern feature that must be added to a website. In reality, traditional WordPress has always relied heavily on server-side rendering.

When someone visits a standard WordPress page, the server runs PHP, retrieves information from the database, processes the active theme and plugins, generates the page’s HTML, and sends that HTML to the visitor’s browser.

However, the term “WordPress SSR” can also refer to two more specialized development approaches:

Each approach has different use cases, benefits, performance considerations, and development requirements.

This guide explains how server-side rendering works in WordPress, how it compares with other rendering methods, how to create a server-rendered Gutenberg block, and when headless WordPress SSR might be appropriate.

What Is Server-Side Rendering?

Server-side rendering, commonly called SSR, is a rendering method in which a server generates a page’s initial HTML before sending it to the visitor’s browser.

A simplified SSR request looks like this:

  1. A visitor requests a page.
  2. The request reaches the web server.
  3. The server retrieves the necessary content and data.
  4. The server generates the HTML.
  5. The completed HTML is sent to the browser.
  6. The browser loads the CSS, images, fonts, and JavaScript.
  7. JavaScript adds interactive behavior where required.

This differs from client-side rendering, where the browser may initially receive a basic HTML shell and then use JavaScript to request data and construct the page.

Because the important content can already be present in the initial HTML response, SSR can make pages easier to display without depending entirely on client-side JavaScript.

However, SSR does not automatically make a website fast. Server processing, database queries, API calls, hosting resources, caching, JavaScript, images, and third-party scripts all influence the final performance.

Subscribe to our Newsletter

Stay updated with our latest news and offers.
Thanks for signing up!

How WordPress Renders a Web Page

A traditional WordPress page request generally follows this process.

1. The visitor requests a URL

The visitor opens a page such as:

https://example.com/blog/wordpress-ssr/

The browser sends an HTTP request to the server hosting the WordPress website.

2. WordPress loads its core environment

The server starts the WordPress loading process. WordPress loads its core files, configuration, active plugins, theme functions, and registered hooks.

3. WordPress determines the requested content

WordPress analyzes the requested URL and uses its query system to determine whether the visitor is requesting a post, page, category archive, search page, product, custom post type, or another resource.

4. WordPress retrieves information from the database

WordPress retrieves the content, settings, metadata, menu information, user permissions, and other data required to build the requested page.

Plugins and themes may perform additional database queries during this stage.

5. WordPress selects the appropriate template

Depending on the theme, WordPress uses either PHP template files or block templates to determine the structure of the page.

A classic theme might use files such as:

single.php
page.php
archive.php
header.php
footer.php

A block theme may use HTML templates, template parts, patterns, and dynamic blocks.

6. PHP generates the page output

WordPress combines the retrieved content with the selected templates. Dynamic blocks and PHP functions generate their output at this stage.

7. WordPress applies hooks and filters

Plugins and themes can modify the content, metadata, scripts, styles, menus, and final HTML through WordPress actions and filters.

8. The server sends HTML to the browser

The completed HTML document is sent to the visitor. The browser parses the document and loads the associated CSS, JavaScript, fonts, images, and other assets.

This process is why standard WordPress can be described as a server-rendered content management system.

The Three Main Forms of WordPress Server-Side Rendering

WordPress SSR development can be divided into three major categories.

1. Traditional PHP-Based WordPress Rendering

Traditional rendering is the default approach used by most WordPress websites.

WordPress runs on the server, retrieves content from its database, processes the active theme and plugins, and produces the final HTML for each request.

This approach is suitable for:

  • Blogs
  • Company websites
  • Online publications
  • Portfolio websites
  • Membership websites
  • WooCommerce stores
  • Educational websites
  • Marketing websites

Traditional WordPress development normally offers the simplest deployment process because the content-management system and presentation layer operate within the same application.

Page caching can prevent PHP and database operations from running for every public visit. When a cached version exists, the server may return the previously generated HTML instead of rebuilding the page.

2. Dynamic Gutenberg Block Rendering

A Gutenberg block can be static or dynamic.

A static block saves its HTML structure in the WordPress database when the post is saved. WordPress retrieves that saved markup when displaying the page.

A dynamic block generates its frontend markup on the server when the page is requested.

Dynamic blocks are especially useful when the output must reflect current information, such as:

  • Latest posts
  • Current post metadata
  • Product prices
  • Stock availability
  • Search forms
  • Navigation
  • User-specific information
  • Related posts
  • Upcoming events
  • Query results

WordPress supports two primary methods for creating dynamic blocks:

  1. Passing a render_callback to register_block_type()
  2. Defining a PHP file through the render property in block.json

The PHP rendering process receives the block attributes, saved content, and block instance.

3. Headless WordPress Server-Side Rendering

In a headless architecture, WordPress manages the content but does not directly control the public-facing frontend.

A separate frontend application retrieves WordPress content through an API, such as:

  • WordPress REST API
  • WPGraphQL
  • A custom WordPress REST endpoint

The frontend may be built with:

  • Next.js
  • Nuxt
  • SvelteKit
  • Astro
  • Another server-capable framework

The frontend framework then decides whether each page should be generated at request time, generated during a build, cached, revalidated, streamed, or rendered in the browser.

Current Next.js applications use Server Components by default in the App Router. Routes can be prerendered or dynamically rendered depending on their data and request-time requirements.

WordPress SSR vs CSR vs SSG vs ISR

Understanding the differences between rendering methods can help you select the right architecture.

Rendering methodWhen HTML is generatedSuitable forMain advantageMain limitation
Server-side renderingWhen a page is requestedFrequently updated or personalized pagesDelivers server-generated HTMLCan increase server processing
Client-side renderingIn the visitor’s browserHighly interactive web applicationsSmooth application-like interactionsImportant content may depend on JavaScript
Static site generationDuring the build processStable pages and documentationFast, cacheable outputContent changes may require a rebuild
Incremental regenerationDuring builds and later revalidationLarge content websitesCombines cached pages with scheduled updatesAdds caching and invalidation complexity
Traditional WordPressUsually at request time or from a page cacheStandard WordPress websitesNative, flexible, and widely supportedRequires server and database optimization
Dynamic Gutenberg blocksWhen WordPress renders the blockContent that changes independently of the postAlways reflects current server dataExpensive callbacks may affect performance

No single method is best for every website.

A news homepage might need dynamic rendering because its content changes frequently. A documentation page may be better suited to static generation. A user dashboard may require request-time rendering, while interactive filters may use client-side rendering after the first page loads.

Modern websites frequently combine several rendering techniques.

Benefits of WordPress Server-Side Rendering

WordPress server-side rendering offers several practical advantages for website performance, accessibility, content delivery, and development flexibility. By generating important page content on the server before sending it to the browser, WordPress can provide a more reliable initial experience while supporting dynamic data, progressive enhancement, and search engine visibility.

Content is available in the initial HTML

Important headings, paragraphs, links, navigation, product information, and other content can be included in the server’s initial response.

This reduces the website’s dependence on JavaScript for displaying essential information.

Better progressive enhancement

A server-rendered page can provide a functional foundation before optional JavaScript features are loaded.

Forms, navigation, links, and content can remain accessible while JavaScript adds enhancements such as filters, live search, sliders, pop-ups, or asynchronous updates.

Easier content discovery for different crawlers

Google can process JavaScript, but server rendering or prerendering still helps users and crawlers receive meaningful content sooner. Google also notes that not every bot can run JavaScript.

Useful for frequently changing content

Dynamic WordPress blocks can display current information without requiring editors to open and update every post containing the block.

For example, a Latest Posts block can automatically display a newly published article.

Centralized markup updates

A dynamic block’s HTML is generated by its server-side PHP template. Updating that template can update every instance of the block across the website.

This can be easier to maintain than modifying saved HTML in hundreds of posts.

Access to WordPress server APIs

Server-rendered components can use:

  • WordPress queries
  • Post metadata
  • Taxonomies
  • User permissions
  • Translation functions
  • PHP libraries
  • WordPress hooks
  • Plugin APIs

Reduced client-side processing for initial content

The visitor’s device does not need to build all initial page content from raw API data. This can be helpful for users on slower devices, although the total benefit depends on how much JavaScript the website loads afterward.

Limitations of WordPress SSR

WordPress server-side rendering provides strong control over how content is generated and delivered, but it also introduces several technical challenges. Server processing, database queries, caching requirements, hosting limitations, and complex frontend integrations can affect performance and scalability. Understanding these limitations helps developers decide when SSR is appropriate and how to optimize it effectively.

Higher server workload

An uncached request may require PHP execution, database queries, plugin processing, API requests, and template rendering.

High-traffic websites may need stronger hosting, caching, database optimization, or load balancing.

Slow Time to First Byte

SSR does not automatically reduce Time to First Byte.

If the server must perform expensive operations before returning HTML, the browser may wait longer for the first response. A slow database, overloaded server, external API, or poorly optimized plugin can increase TTFB.

More complex caching

Dynamic content creates difficult caching decisions.

A website may need different responses for:

Incorrect caching can show stale or inappropriate information.

Hydration costs

A server-rendered JavaScript application may still need to download and execute JavaScript before its interactive components become usable.

Sending HTML from the server does not eliminate the cost of a large JavaScript bundle.

More complicated headless deployment

A headless WordPress setup introduces another application, hosting environment, deployment workflow, caching layer, and potential source of errors.

Editors may also need custom preview and publishing workflows.

Plugin compatibility challenges

Some WordPress plugins assume that the active WordPress theme controls the frontend. These plugins may not work automatically in a headless frontend.

Forms, SEO metadata, redirects, search, memberships, authentication, previews, and WooCommerce features may need additional integration.

How to Create a Server-Rendered Gutenberg Block

The following example creates a dynamic Latest Posts block.

Editors can select how many posts to display, while PHP generates the final list on the server.

Step 1: Create the Block Plugin

A simple plugin structure might look like this:

codecanel-latest-posts/
├── codecanel-latest-posts.php
├── src/
│   └── latest-posts/
│       ├── block.json
│       ├── edit.js
│       ├── index.js
│       ├── render.php
│       └── style.scss
├── build/
└── package.json

For a real project, the official @wordpress/create-block package can generate much of the initial structure.

Step 2: Register the Block Plugin

Create the main plugin file:

<?php
/**
 * Plugin Name: CodeCanel Latest Posts Block
 * Description: Displays a configurable list of recent WordPress posts.
 * Version: 1.0.0
 * Author: CodeCanel
 * Text Domain: codecanel
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * Registers the dynamic block.
 */
function codecanel_register_latest_posts_block() {
	register_block_type( __DIR__ . '/build/latest-posts' );
}
add_action( 'init', 'codecanel_register_latest_posts_block' );

Using the generated build directory ensures that WordPress loads the compiled JavaScript, CSS, metadata, and PHP rendering file.

Step 3: Configure block.json

Create the following block metadata:

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "codecanel/latest-posts",
  "version": "1.0.0",
  "title": "Latest Posts",
  "category": "widgets",
  "icon": "list-view",
  "description": "Displays a server-rendered list of recent posts.",
  "keywords": ["posts", "recent posts", "latest posts"],
  "textdomain": "codecanel",
  "attributes": {
    "numberOfPosts": {
      "type": "number",
      "default": 5
    }
  },
  "editorScript": "file:./index.js",
  "style": "file:./style-index.css",
  "render": "file:./render.php"
}

WordPress recommends block.json as the canonical way to register block metadata. The current Block API version is 3, and the render property can identify the PHP file used to generate frontend output.

Step 4: Create the JavaScript Registration File

Create index.js:

import { registerBlockType } from '@wordpress/blocks';
import Edit from './edit';
import './style.scss';

registerBlockType('codecanel/latest-posts', {
	edit: Edit,
	save: () => null,
});

Returning null from save means WordPress stores the block delimiter and attributes instead of fixed frontend HTML.

The PHP rendering file will control the frontend output.

Step 5: Build the Editor Interface

Create edit.js:

import {
	InspectorControls,
	useBlockProps,
} from '@wordpress/block-editor';

import {
	Notice,
	PanelBody,
	RangeControl,
	Spinner,
} from '@wordpress/components';

import { RawHTML } from '@wordpress/element';
import { useServerSideRender } from '@wordpress/server-side-render';

export default function Edit({ attributes, setAttributes }) {
	const { numberOfPosts } = attributes;
	const blockProps = useBlockProps();

	const { content, status, error } = useServerSideRender({
		block: 'codecanel/latest-posts',
		attributes,
	});

	return (
		<>
			<InspectorControls>
				<PanelBody title="Latest Posts Settings">
					<RangeControl
						label="Number of posts"
						value={numberOfPosts}
						onChange={(value) =>
							setAttributes({
								numberOfPosts: value,
							})
						}
						min={1}
						max={10}
					/>
				</PanelBody>
			</InspectorControls>

			<div {...blockProps}>
				{status === 'loading' && <Spinner />}

				{status === 'error' && (
					<Notice status="error" isDismissible={false}>
						{error?.message ||
							'The latest posts preview could not be loaded.'}
					</Notice>
				)}

				{status === 'success' && (
					<RawHTML>{content}</RawHTML>
				)}
			</div>
		</>
	);
}

The useServerSideRender hook retrieves a server-rendered block preview, manages loading and error states, and debounces repeated requests. It can use GET or POST depending on the request payload.

Step 6: Generate the Frontend Output in PHP

Create render.php:

<?php
/**
 * Server-side rendering for the Latest Posts block.
 *
 * Available variables:
 *
 * @var array    $attributes Block attributes.
 * @var string   $content    Saved block content.
 * @var WP_Block $block      Current block instance.
 */

if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

$number_of_posts = isset( $attributes['numberOfPosts'] )
	? absint( $attributes['numberOfPosts'] )
	: 5;

$number_of_posts = max( 1, min( 10, $number_of_posts ) );

$latest_posts_query = new WP_Query(
	array(
		'post_type'              => 'post',
		'post_status'            => 'publish',
		'posts_per_page'         => $number_of_posts,
		'ignore_sticky_posts'    => true,
		'no_found_rows'          => true,
		'update_post_meta_cache' => false,
		'update_post_term_cache' => false,
	)
);
?>

<div
	<?php
	echo get_block_wrapper_attributes(
		array(
			'class' => 'codecanel-latest-posts',
		)
	);
	?>
>
	<?php if ( $latest_posts_query->have_posts() ) : ?>
		<ul class="codecanel-latest-posts__list">
			<?php while ( $latest_posts_query->have_posts() ) : ?>
				<?php $latest_posts_query->the_post(); ?>

				<li class="codecanel-latest-posts__item">
					<a
						class="codecanel-latest-posts__link"
						href="<?php echo esc_url( get_permalink() ); ?>"
					>
						<?php echo esc_html( get_the_title() ); ?>
					</a>
				</li>
			<?php endwhile; ?>
		</ul>

		<?php wp_reset_postdata(); ?>
	<?php else : ?>
		<p class="codecanel-latest-posts__empty">
			<?php
			echo esc_html__(
				'No published posts were found.',
				'codecanel'
			);
			?>
		</p>
	<?php endif; ?>
</div>

This implementation:

  • Validates the number of posts
  • Limits the accepted range
  • Retrieves only published posts
  • Avoids unnecessary pagination calculations
  • Escapes URLs and text
  • Resets global post data
  • Provides an empty state
  • Uses WordPress block wrapper attributes

Because render.php can run for every instance of the block, avoid declaring reusable functions or classes directly inside the file. Shared logic should be placed elsewhere and called from the rendering template.

Step 7: Add Basic Styling

Create style.scss:

.wp-block-codecanel-latest-posts {
	padding: 1.25rem;
	border: 1px solid currentColor;
	border-radius: 0.5rem;
}

.codecanel-latest-posts__list {
	margin: 0;
	padding-left: 1.25rem;
}

.codecanel-latest-posts__item + .codecanel-latest-posts__item {
	margin-top: 0.5rem;
}

.codecanel-latest-posts__link {
	font-weight: 600;
}

Use theme-compatible values rather than hardcoded branding colors when the block must work across different WordPress themes.

Step 8: Build and Test the Plugin

Run the appropriate build commands:

npm install
npm run build

Activate the plugin, insert the Latest Posts block, change the number of posts, and test both the editor preview and frontend output.

Test the block under the following conditions:

  • No published posts
  • One published post
  • Ten published posts
  • Long post titles
  • Different themes
  • Mobile devices
  • Plugin deactivation
  • REST API errors
  • JavaScript disabled on the frontend

render.php vs render_callback

Both methods generate dynamic block output in PHP.

Use render.php when:

  • The block is registered through block.json
  • You want to separate markup from registration logic
  • The rendering template contains a meaningful amount of HTML
  • You prefer a template-based development workflow

Use render_callback when:

  • The block is PHP-only
  • The rendering logic is short
  • The callback needs to be registered conditionally
  • The output is easier to manage in a reusable PHP function

A callback-based version might look like this:

function codecanel_render_dynamic_message_block( $attributes ) {
	$message = isset( $attributes['message'] )
		? sanitize_text_field( $attributes['message'] )
		: __( 'Hello from WordPress.', 'codecanel' );

	return sprintf(
		'<div %1$s>%2$s</div>',
		get_block_wrapper_attributes(),
		esc_html( $message )
	);
}

function codecanel_register_dynamic_message_block() {
	register_block_type(
		__DIR__ . '/build/dynamic-message',
		array(
			'render_callback' => 'codecanel_render_dynamic_message_block',
		)
	);
}
add_action( 'init', 'codecanel_register_dynamic_message_block' );

WordPress officially supports both rendering methods.

When Not to Use ServerSideRender for the Editor

The ServerSideRender component and useServerSideRender hook are useful when the editor preview must reuse existing PHP rendering logic.

However, they should not automatically become the data layer for every new block.

A dedicated REST endpoint or WordPress data store may provide a better editor experience when the block requires:

  • Fast live filtering
  • Search suggestions
  • Pagination
  • Complex forms
  • Frequent attribute changes
  • Multiple independent API requests
  • Client-side data manipulation

Use server-rendered previews when reusing PHP output provides a meaningful development or consistency benefit.

Headless WordPress SSR With Next.js

A headless website separates the WordPress backend from the public frontend.

WordPress handles:

Next.js handles:

  • Public routes
  • Page layout
  • Data fetching
  • Metadata
  • Rendering
  • Caching
  • Client-side interactions

A simplified Next.js App Router page could retrieve a post by its WordPress slug.

import { notFound } from 'next/navigation';

type WordPressPost = {
	id: number;
	slug: string;
	title: {
		rendered: string;
	};
	content: {
		rendered: string;
	};
};

async function getPost(slug: string): Promise<WordPressPost | null> {
	const apiBaseUrl = process.env.WORDPRESS_API_URL;

	if (!apiBaseUrl) {
		throw new Error('WORDPRESS_API_URL is not configured.');
	}

	const requestUrl =
		`${apiBaseUrl}/wp-json/wp/v2/posts` +
		`?slug=${encodeURIComponent(slug)}`;

	const response = await fetch(requestUrl, {
		cache: 'no-store',
	});

	if (!response.ok) {
		throw new Error(
			`WordPress request failed with status ${response.status}.`
		);
	}

	const posts: WordPressPost[] = await response.json();

	return posts[0] ?? null;
}

export default async function BlogPostPage({
	params,
}: {
	params: Promise<{ slug: string }>;
}) {
	const { slug } = await params;
	const post = await getPost(slug);

	if (!post) {
		notFound();
	}

	return (
		<main>
			<article>
				<h1
					dangerouslySetInnerHTML={{
						__html: post.title.rendered,
					}}
				/>

				<div
					dangerouslySetInnerHTML={{
						__html: post.content.rendered,
					}}
				/>
			</article>
		</main>
	);
}

The example uses request-time fetching. In a production project, you must also plan:

  • HTML sanitization and trusted content
  • Dynamic metadata
  • Canonical URLs
  • Open Graph images
  • Preview mode
  • Draft authentication
  • Pagination
  • Redirects
  • WordPress errors
  • API caching
  • Cache invalidation
  • Sitemap generation
  • Search
  • Forms
  • Image optimization
  • Content updates
  • Deployment failures

Next.js App Router pages and layouts are Server Components by default. Server rendering can happen during prerendering or dynamically at request time, depending on the route and data requirements.

Traditional WordPress vs Headless WordPress SSR

Traditional WordPress and headless WordPress SSR both generate content on the server, but they use different architectures. Traditional WordPress manages the content, templates, plugins, and frontend within one system, while a headless setup uses WordPress as the content backend and a separate framework to build and render the public website.

The right option depends on your project’s flexibility, performance, integration, maintenance, and development requirements.

RequirementTraditional WordPressHeadless WordPress
Initial setupSimplerMore complex
Content editingNativeNative backend with custom frontend preview considerations
Theme compatibilityStrongTraditional themes are not used
Plugin frontend compatibilityUsually strongVaries by plugin
HostingOne primary applicationUsually two environments
Developer skillsPHP, HTML, CSS, JavaScriptWordPress plus frontend framework expertise
DeploymentStandard WordPress workflowSeparate frontend and backend deployments
CachingWordPress caching ecosystemMust coordinate frontend, API, and WordPress caching
FlexibilityHighVery high
Maintenance costUsually lowerUsually higher

Headless WordPress is not automatically faster or more modern in a way that benefits every business.

For an ordinary blog, company website, portfolio, or WooCommerce store, traditional WordPress may be easier to build, maintain, optimize, and hand over.

Headless development makes more sense when the project has clear requirements that traditional WordPress cannot efficiently satisfy.

WordPress Interactivity API and SSR

The WordPress Interactivity API provides a standard approach for adding interactive frontend behavior to blocks.

It allows WordPress to generate the initial, state-aware HTML on the server. Client-side JavaScript can then handle later interactions without requiring a full page reload.

The process can combine:

  • PHP server rendering
  • WordPress block markup
  • Initial state and context
  • Declarative directives
  • Client-side actions
  • Progressive enhancement

To mark a block as interactive, its block.json can include:

{
  "supports": {
    "interactivity": true
  }
}

A block can then use WordPress interactivity directives in its server-rendered markup.

The Interactivity API was introduced in WordPress 6.5 and is used by WordPress core blocks. WordPress processes supported directives on the server so that the initial HTML already contains the appropriate state before client-side behavior takes over.

This approach is useful for features such as:

  • Counters
  • Accordions
  • Pop-ups
  • Product filters
  • Shopping-cart interfaces
  • Live search
  • Interactive navigation
  • Favorite buttons
  • Expandable content

It provides WordPress developers with a native path for building interactive blocks without replacing WordPress’s server-rendering foundation.

How Caching Works With WordPress SSR

Caching is not a separate type of server-side rendering.

It is an optimization layer that can store the result of expensive operations so that WordPress does not repeat them unnecessarily.

Full-page caching

Full-page caching stores the completed HTML response for a page.

Instead of running WordPress, querying the database, and generating the HTML for every visitor, the server can return the cached HTML.

This is especially effective for public pages that show the same content to every visitor.

Object caching

Object caching stores the results of expensive database operations.

Persistent object-cache systems such as Redis or Memcached can preserve cached objects between separate page requests.

Browser caching

Browser caching allows static files such as CSS, JavaScript, images, and fonts to remain on the visitor’s device for a defined period.

CDN caching

A content delivery network can store cacheable content at geographically distributed locations.

This reduces the distance between the visitor and the cached resource.

PHP opcode caching

Opcode caching stores compiled PHP instructions so the server does not need to repeatedly parse and compile the same PHP files.

Application-level caching

Developers can cache expensive query results or API responses with the WordPress Transients API, object cache, or another controlled caching layer.

Cache invalidation

Every caching strategy needs a plan for removing stale data.

A cache may need to be cleared when:

  • A post is published
  • A post is updated
  • A product price changes
  • Inventory changes
  • A menu is edited
  • A user logs in
  • A theme option changes
  • An API response expires

Caching should be planned as part of the rendering architecture rather than installed as an afterthought.

Does Server-Side Rendering Improve WordPress SEO?

SSR can support SEO, but it does not guarantee higher rankings.

Server rendering can ensure that important content, headings, links, metadata, and structured information are available in the initial HTML response.

This can reduce the website’s dependence on JavaScript rendering and make content easier for different crawlers to process.

Google can render JavaScript, but Google still recommends server-side rendering, static rendering, or hydration instead of using dynamic rendering as a permanent workaround. Dynamic rendering refers to serving one version to bots and a different version to users.

Search visibility still depends on factors such as:

  • Search intent
  • Content quality
  • Topical relevance
  • Internal linking
  • Backlinks
  • Crawlability
  • Indexability
  • Metadata
  • Structured data
  • Mobile usability
  • Page experience
  • Website reputation
  • Content originality

A slow, thin, or irrelevant server-rendered page will not outrank a useful page simply because it uses SSR.

WordPress SSR and Core Web Vitals

Server-side rendering affects performance, but it is only one part of the full page-loading process.

Largest Contentful Paint

Largest Contentful Paint measures how quickly the main visible content appears.

SSR may help make the main content available earlier, but LCP can still be delayed by:

  • Slow server responses
  • Large images
  • Render-blocking CSS
  • Web fonts
  • Client-side hydration
  • JavaScript
  • Third-party scripts

Interaction to Next Paint

Interaction to Next Paint measures responsiveness.

A server-rendered page may still have poor responsiveness if it loads and executes excessive JavaScript or performs expensive work after a user interaction.

Cumulative Layout Shift

Cumulative Layout Shift measures visual stability.

SSR does not prevent layout shifts caused by:

  • Images without dimensions
  • Advertisements
  • Dynamically inserted banners
  • Late-loading fonts
  • Pop-ups
  • Cookie notices
  • Asynchronously loaded components

Google’s recommended “good” thresholds are an LCP within 2.5 seconds, an INP below 200 milliseconds, and a CLS below 0.1.

Time to First Byte

TTFB measures how long it takes for the browser to begin receiving the server response.

Server-side rendering can increase TTFB when the server performs expensive work. Improve it through:

Measure the complete experience rather than assuming that one rendering method will solve every performance issue.

Common WordPress SSR Problems and Solutions

WordPress SSR issues often come from slow server processing, inefficient database queries, caching conflicts, block-rendering errors, or differences between server and client output. Identifying the underlying cause helps developers improve performance, maintain consistent content, and prevent errors in both traditional and headless WordPress environments.

ProblemPossible causeRecommended solution
High TTFBSlow PHP, database queries, APIs, or hostingProfile the request, enable caching, and optimize slow operations
Dynamic block is emptyPHP error or query returns no dataCheck logs and include a safe empty state
Editor preview does not updateAttributes are not passed correctlyVerify the block name, attributes, and REST response
Too many preview requestsAttributes change rapidlyUse the debounced hook or a dedicated data endpoint
Frontend and editor look differentMissing editor stylesLoad shared styles and add editor-specific rules only when needed
Block validation errorDynamic markup was saved as static HTMLReturn null from save or maintain compatible saved markup
Stale contentCached HTML was not invalidatedPurge related caches after content changes
Slow dynamic blockExpensive query runs for every block instanceOptimize the query and cache reusable results
Hydration mismatchServer and client produce different initial markupUse consistent initial data and HTML
Missing headless metadataFrontend does not retrieve SEO informationGenerate metadata in the frontend application
Headless preview failsDraft request lacks authenticationCreate a secure authenticated preview workflow
Plugin feature disappearsPlugin depends on theme templatesRebuild or integrate the feature in the frontend
Logged-in data is cachedPublic and private cache rules overlapExclude personalized responses from public caching
Duplicate contentMultiple frontend URLs expose the same postUse canonical URLs and consistent routing
API request failsWordPress is unavailable or rate-limitedAdd timeouts, error boundaries, logging, and fallbacks

WordPress SSR Security Best Practices

Server rendering handles data that may come from block attributes, post content, user input, APIs, or the database.

Follow these practices:

Sanitize input

Use the appropriate WordPress sanitization function before storing or processing values.

Examples include:

sanitize_text_field()
sanitize_email()
absint()
sanitize_key()
esc_url_raw()

Escape output

Escape values according to where they appear.

Examples include:

esc_html()
esc_attr()
esc_url()
wp_kses_post()

Validate block attributes

Do not assume that block attributes always contain the expected type or range.

Convert and limit numeric values before using them in queries.

Check permissions

Any server endpoint that exposes private, draft, account, or administrative data must verify the user’s capabilities.

Use nonces appropriately

Use WordPress nonces for requests that perform protected actions. A nonce should not replace capability checks.

Protect API credentials

Do not expose private API keys or backend credentials in client-side JavaScript.

Avoid unsafe HTML

Only render trusted HTML. Sanitize content when it can include untrusted or externally supplied markup.

WordPress SSR Development Best Practices

  1. Use block.json for block registration. Keep block metadata in one canonical location.
  2. Use dynamic rendering only when the output needs it. Static blocks can be simpler and require less server processing.
  3. Escape every dynamic value. Match the escaping function to the output context.
  4. Validate attributes before using them. Apply defaults and acceptable limits.
  5. Avoid unnecessary database queries. Retrieve only the fields and records the block needs.
  6. Reset post data after custom loops. Prevent custom queries from affecting later page content.
  7. Cache expensive operations. Use an appropriate cache with a clear expiration and invalidation strategy.
  8. Provide loading, error, and empty states. Do not leave editors or visitors with an unexplained blank area.
  9. Keep frontend and editor styles aligned. The preview should represent the published result accurately.
  10. Avoid declaring functions in render.php. The rendering file can be loaded more than once on a page.
  11. Use dedicated endpoints for complex editor interfaces. Do not make full HTML rendering the default solution for every data request.
  12. Test with and without JavaScript. Essential content should remain available when progressive enhancement is the goal.
  13. Inspect the original HTML response. Do not rely only on the browser’s modified DOM when verifying SSR.
  14. Measure real performance. Use field data, server profiling, browser tools, and Search Console.
  15. Test caching for logged-in users. Personalized content must not leak into shared caches.
  16. Document the architecture. Explain where content is rendered, cached, refreshed, and invalidated.

How to Choose the Right WordPress Rendering Method

Use traditional WordPress rendering when:

  • You are building a standard content website
  • Theme and plugin compatibility is important
  • Editors need native previews
  • The development team specializes in WordPress
  • You want a simpler hosting and deployment workflow

Use dynamic Gutenberg blocks when:

  • A block must show current server data
  • The same markup appears in many posts
  • Output should change without resaving posts
  • PHP APIs or WordPress queries are required
  • Centralized markup maintenance is valuable

Use headless WordPress SSR when:

  • WordPress content must appear in a larger application
  • The frontend requires a specialized framework
  • Multiple channels consume the same content API
  • The team can maintain separate applications
  • Custom frontend performance and interaction requirements justify the complexity

Use static generation when:

  • Content changes infrequently
  • Pages can be generated during builds
  • Very fast cached delivery is a priority
  • Rebuild or revalidation workflows are acceptable

Use client-side rendering selectively when:

  • A component is highly interactive
  • The data changes after the initial page load
  • Browser APIs are required
  • The feature is not essential to the initial document
  • A server-rendered foundation is already available

Final Thoughts

WordPress server-side rendering is not one single technology.

It can refer to the traditional PHP rendering used by ordinary WordPress websites, the dynamic PHP rendering used by Gutenberg blocks, or a separate server-rendered frontend connected to a headless WordPress backend.

For most WordPress websites, traditional rendering combined with effective page caching, optimized database queries, strong hosting, and selective JavaScript is a practical solution.

Dynamic Gutenberg blocks are valuable when content must remain current or when developers need centralized control over block markup.

Headless WordPress SSR provides greater frontend flexibility, but that flexibility comes with additional development, deployment, caching, preview, SEO, and maintenance responsibilities.

The best approach is not the one with the newest terminology. It is the architecture that delivers accurate content, reliable performance, maintainable code, and a smooth experience for both website visitors and content editors.

Frequently Asked Questions

Is WordPress server-side rendered?

Yes. Traditional WordPress uses PHP and database queries to generate HTML on the server before sending it to the browser.

What is Gutenberg server-side rendering?

Gutenberg server-side rendering means that a block’s frontend HTML is generated by PHP when WordPress renders the page. The block can use a render_callback or a render.php file.

What is a dynamic WordPress block?

A dynamic block generates its output at page-rendering time instead of relying entirely on HTML stored when the editor saved the post.

When should I use a dynamic block?

Use a dynamic block when its content needs to reflect current data, user information, query results, changing metadata, or centrally maintained markup.

Does server-side rendering automatically improve SEO?

No. SSR can make content available in the initial HTML, but rankings still depend on relevance, quality, authority, technical accessibility, and many other signals.

Does SSR reduce TTFB?

Not automatically. Generating HTML on every request can increase TTFB. Caching, efficient queries, fast hosting, and server optimization are usually required.

Is caching a type of SSR?

No. Caching is an optimization that stores previously generated data or HTML. It can be used with server-side rendering, static generation, APIs, and other architectures.

Is SSR better than client-side rendering?

It depends on the page. SSR is helpful for initial content, while client-side rendering can be useful for highly interactive behavior after the page loads.

What is the difference between a static and dynamic Gutenberg block?

A static block saves its frontend markup in the database. A dynamic block uses PHP to generate its frontend markup when the page is rendered.

Should I use render.php or render_callback?

Use render.php for template-oriented block markup registered through block.json. Use render_callback when a reusable PHP function or programmatic registration is more appropriate.

This page was last edited on 20 July 2026, at 6:08 pm