{"id":1064,"date":"2026-08-31T10:54:28","date_gmt":"2026-08-31T08:54:28","guid":{"rendered":"https:\/\/timotheacreations.com\/?p=1064"},"modified":"2026-09-11T09:03:38","modified_gmt":"2026-09-11T07:03:38","slug":"the-blank-header-that-fixes-itself-in-thirty-minutes","status":"publish","type":"post","link":"https:\/\/timotheacreations.com\/fr\/the-blank-header-that-fixes-itself-in-thirty-minutes\/","title":{"rendered":"The blank header that fixes itself in thirty minutes"},"content":{"rendered":"<p class=\"is-style-lead wp-block-paragraph\">A new block pattern renders as nothing. No error, no warning, no failed request \u2014 just empty space where your header used to be. Then, half an hour later, it works.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">I spent the first twenty minutes of a working day convinced I had broken four websites at once.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The task was ordinary. I was making the strings in four themes translatable, and the standard way to do that is the way Twenty Twenty-Five does it: a template part that contains user-visible text becomes a one-line reference to a PHP pattern, because <code>.html<\/code> files are served verbatim and never execute PHP. So <code>parts\/header.html<\/code> stopped being markup and became this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>&lt;!-- wp:pattern {\"slug\":\"athena\/header\"} \/--&gt;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">\u2026with the real markup moved into <code>patterns\/header.php<\/code>, where <code>esc_html__()<\/code> works.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Then I loaded the site and there was no header. No footer either. Not a broken header \u2014 <em>no header<\/em>. The page began at the first content section and ended at the last one.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Nothing was wrong, according to everything<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">No PHP error. No entry in the log. <code>WP_DEBUG<\/code> on: silence. The pattern file was sitting on disk exactly where the reference pointed, with balanced block comments and valid markup. View source showed no trace that a block had been there at all \u2014 not a comment, not an empty <code>&lt;div&gt;<\/code>. The header had simply never existed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And it was happening on all four sites, which ruled out my usual suspicion that I had mistyped something.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\" style=\"border-left-width:2px;padding-left:var(--wp--preset--spacing--40)\">A failure with no error message is not a bug you debug. It is a behaviour you have not read about yet.<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">An unregistered pattern is not an error<\/h2>\n\n\n\n<p class=\"is-style-default wp-block-paragraph\">The first half of the answer is eleven lines into <code>render_block_core_pattern()<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ wp-includes\/blocks\/pattern.php\nfunction render_block_core_pattern( $attributes ) {\n    \/\/ \u2026\n    $registry = WP_Block_Patterns_Registry::get_instance();\n\n    if ( ! $registry-&gt;is_registered( $slug ) ) {\n        return '';\n    }<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">An unrecognized slug renders as an empty string.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">That is the whole failure mode. A pattern slug WordPress does not recognize produces <strong>an empty string<\/strong>. Not a warning, not a placeholder, not an HTML comment you could find in view-source. From the browser&#8217;s point of view the block was never in the template.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Which turns the question into a much better one: why was a pattern file that was plainly <em>there<\/em> not registered?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">WordPress caches the list of your pattern files<\/h2>\n\n\n\n<p class=\"is-style-default wp-block-paragraph\">It does not scan <code>patterns\/<\/code> on every request. It scans once and remembers, in a site transient:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ wp-includes\/class-wp-theme.php\nprivate function get_pattern_cache() {\n    \/\/ \u2026\n    $pattern_data = get_site_transient( 'wp_theme_files_patterns-' . $this-&gt;cache_hash );\n\n    if ( is_array( $pattern_data ) &amp;&amp; $pattern_data&#91;'version'] === $this-&gt;get( 'Version' ) ) {\n        return $pattern_data&#91;'patterns'];\n    }\n    return false;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The guard clause is the interesting line.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Read that condition again. The cache is considered valid when the stored version matches <code>$this-&gt;get( 'Version' )<\/code> \u2014 <strong>the version string in your <code>style.css<\/code> header.<\/strong><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Not the files&#8217; modification times. Not a hash of the directory. Not the number of files in it. A string you type by hand.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">And it is stored with a deliberate lifetime:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>private static $cache_expiration = 1800;<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Thirty minutes.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">So here is what actually happened to me. WordPress had scanned <code>patterns\/<\/code> at some point that morning, found the files that existed <em>at that moment<\/em>, stored the list, and stamped it with the theme&#8217;s version. I then added <code>patterns\/header.php<\/code> and did not touch <code>Version<\/code>, because why would I \u2014 I was moving markup around, not making a release.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The stamp still matched. The cache was still valid. My new file was not in the list, so the pattern was never registered, so the reference to it rendered as an empty string.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For up to thirty minutes. Which is exactly why it appeared to heal itself while I was still trying to work out how to reproduce it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The asymmetry that makes it feel like a haunting<\/h2>\n\n\n\n<p class=\"is-style-default wp-block-paragraph\">Here is the part I find genuinely interesting, and the reason this is so disorienting when you hit it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>Deleting<\/strong> a pattern file behaves completely differently:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ wp-includes\/block-patterns.php\nif ( ! file_exists( $file_path ) ) {\n    _doing_it_wrong(\n        __FUNCTION__,\n        sprintf(\n            __( 'Could not register file \"%s\" as a block pattern as the file does not exist.' ),\n            $file\n        ),\n        '6.4.0'\n    );\n    $theme-&gt;delete_pattern_cache();\n    continue;\n}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A missing file is noticed, announced, and self-healed.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Remove a pattern file and WordPress walks its cached list, finds the file gone, tells you about it, and throws the cache away. The system corrects itself and complains while doing it.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><strong>There is no equivalent path for a file you add.<\/strong> Nothing is looking for one. Nothing could be, without the directory scan the cache exists to avoid.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">A deletion announces itself. An addition sits in the dark until the timer runs out.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">While you are working: switch the cache off<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><code>get_block_patterns()<\/code> opens with an escape hatch:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>$can_use_cached = ! wp_is_development_mode( 'theme' );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">So in <code>wp-config.php<\/code> on your local site:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>define( 'WP_DEVELOPMENT_MODE', 'theme' );<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That makes the whole class of problem disappear while you are building. It is the single most useful line in a block-theme developer&#8217;s config, and I now put it in every local install before I write anything.<\/p>\n\n\n\n<p class=\"is-style-eyebrow wp-block-paragraph\"><strong>Local only.<\/strong> This turns off a cache that exists for good reasons. It has no place on a production site, and no place on a site you have handed to someone else.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">When you ship: bump the version, every time<\/h2>\n\n\n\n<p class=\"is-style-default wp-block-paragraph\">This is the part that matters if other people install your theme, and it is why I am writing it down.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Picture a release that adds one pattern file. Your customer uploads the new folder over the old one \u2014 no error, files land, everything looks fine. But if <code>Version<\/code> in <code>style.css<\/code> is unchanged, the cached list on <em>their<\/em> server is still stamped valid. Their site keeps the old list. The new pattern is not registered. Anything referencing it renders as nothing.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If that pattern happens to be their header, their site loses its header for half an hour, and the only person they can ask is you.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">&#8220;Bump the version on every release&#8221; is advice you have read a hundred times and probably think of as bookkeeping \u2014 semantic versioning, changelogs, tidiness. It is not bookkeeping. In a block theme it is <strong>a cache-invalidation key<\/strong>, and skipping it has a specific, silent, user-visible failure attached.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It is also worth knowing which way round the rule runs. You do not bump the version <em>because<\/em> you added a pattern. You bump it because you are releasing \u2014 and the pattern cache is one of several things quietly relying on you to do so.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">En bref<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A pattern slug WordPress does not recognize renders as <strong>an empty string<\/strong> \u2014 no error anywhere.<\/li>\n\n\n\n<li>The list of pattern files is cached in a site transient for <strong>30 minutes<\/strong>, keyed on your theme&#8217;s <code>Version<\/code>.<\/li>\n\n\n\n<li>A <strong>deleted<\/strong> pattern file clears that cache and warns you. An <strong>added<\/strong> one does neither.<\/li>\n\n\n\n<li>Locally: <code>define( 'WP_DEVELOPMENT_MODE', 'theme' );<\/code><\/li>\n\n\n\n<li>On release: change <code>Version<\/code> in <code>style.css<\/code>, ou vos visiteurs voient l\u2019en-t\u00eate vide \u00e0 votre place.<\/li>\n<\/ul>\n\n\n\n<pre class=\"wp-block-verse\"><em>I build a small family of WordPress block themes \u2014 Athena, Apollo, Hera and Hermes \u2014 and most of what I write here is something that cost me an afternoon first. Every release in the family bumps its version whether or not it feels like it deserves one, for exactly the reason above.<\/em><\/pre>","protected":false},"excerpt":{"rendered":"<p>A new block pattern renders as nothing. No error, no warning, no failed request \u2014 just empty space where your header used to be. Then, half an hour later, it works.<\/p>\n<p>I spent the first twenty minutes of a working day convinced I had broken four websites at once.<\/p>","protected":false},"author":265843788,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1367,1414],"tags":[],"class_list":["post-1064","post","type-post","status-publish","format-standard","hentry","category-wordpress","category-block-themes"],"_links":{"self":[{"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/posts\/1064","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/users\/265843788"}],"replies":[{"embeddable":true,"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/comments?post=1064"}],"version-history":[{"count":2,"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/posts\/1064\/revisions"}],"predecessor-version":[{"id":1066,"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/posts\/1064\/revisions\/1066"}],"wp:attachment":[{"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/media?parent=1064"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/categories?post=1064"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/timotheacreations.com\/fr\/wp-json\/wp\/v2\/tags?post=1064"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}