{"id":17,"date":"2026-06-06T21:48:34","date_gmt":"2026-06-06T21:48:34","guid":{"rendered":"https:\/\/softwareproduction.eu\/wordpress\/?p=17"},"modified":"2026-06-07T01:21:55","modified_gmt":"2026-06-07T01:21:55","slug":"advanced-cms-patterns-conditional-rendering-live-preview","status":"publish","type":"post","link":"https:\/\/softwareproduction.eu\/?p=17","title":{"rendered":"Advanced CMS Patterns: Conditional Rendering &#038; Live Preview"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Modern CMS-driven frontends live in a constant tension: editors want <strong>instant visual feedback<\/strong> and <strong>full control over what appears, when, and to whom<\/strong>, while developers need <strong>SSR correctness<\/strong>, <strong>bundle discipline<\/strong>, and <strong>maintainable architectures<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This masterclass walks through two complementary patterns that resolve that tension on a <strong>Nuxt 4 + Contentful<\/strong> stack:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Real-time WYSIWYG editing with Contentful Live Preview in SSR<\/strong><\/li>\n<li><strong>Content-driven conditional rendering via an evaluator plugin system<\/strong><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Together, they give editors a \u201cwhat you see is what you ship\u201d experience while keeping the codebase modular, testable, and production-ready.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Real-Time WYSIWYG Editing in SSR with Contentful Live Preview<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">In a headless CMS world, content editors work in one interface and verify results in another. The classic loop:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">> edit \u2192 save \u2192 preview \u2192 check \u2192 repeat<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Every minor tweak requires a full page reload. Contentful\u2019s <strong>Live Preview SDK<\/strong> promises to break this loop: edits in the sidebar instantly update the embedded preview iframe, no reload required. The reality with Nuxt SSR is more nuanced \u2014 the docs cover the essentials, but integrating this into an SSR pipeline requires careful architecture.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Three-Level Window Hierarchy<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The first obstacle is structural. Live Preview embeds the site inside Contentful using a chain of iframes:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TD\n  A[\"Window Level 1:&lt;br\/&gt;Contentful Web App&lt;br\/&gt;(contentful.com)\"]:::level1\n  A --&gt; B[\"Window Level 2:&lt;br\/&gt;Contentful Preview Frame&lt;br\/&gt;(internal iframe)\"]:::level2\n  B --&gt; C[\"Window Level 3:&lt;br\/&gt;Your Application&lt;br\/&gt;(your-app.example.com)&lt;br\/&gt;&lt;br\/&gt;This is where your Nuxt app runs.&lt;br\/&gt;postMessage must traverse TWO levels&lt;br\/&gt;to reach Contentful's listener.\"]:::level3\n\n  classDef level1 fill:#f5f5f5,stroke:#555,stroke-width:1px;\n  classDef level2 fill:#ffffff,stroke:#777,stroke-width:1px;\n  classDef level3 fill:#e8f5ff,stroke:#2276c9,stroke-width:1px;<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The application (Level 3) must send <code>postMessage<\/code> calls that ultimately reach Contentful (Level 1). But <code>postMessage<\/code> only talks to the <strong>immediate parent<\/strong>, not <code>window.top<\/code>. To be robust, messaging code needs to handle both:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>window.parent<\/code> (Contentful\u2019s preview iframe)<\/li>\n<li><code>window.top<\/code> (the main Contentful web app)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Miss one of these and messages will quietly vanish.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">How Live Preview Works<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Contentful\u2019s Live Preview provides two core capabilities that you wire into a Nuxt app:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Inspector Mode (click-to-edit)<\/strong><\/li>\n<li><strong>Live Updates (real-time content sync)<\/strong><\/li>\n<\/ol>\n\n\n\n<p class=\"wp-block-paragraph\">Both sit on top of the iframe messaging model.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">1. Inspector Mode (Click-to-Edit)<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">With Inspector Mode enabled, hovering over content in the preview shows a blue overlay with the field name. Clicking it jumps the CMS sidebar to that exact field.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">This is powered by <strong>DOM data attributes<\/strong>:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>data-contentful-entry-id<\/code><\/li>\n<li><code>data-contentful-field-id<\/code><\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The Live Preview SDK scans the DOM for these and wires up the overlay and click behavior.<\/p>\n\n\n\n<pre><code class=\"language-html\">&lt;!-- Rendered HTML: --&gt;\n&lt;h1\n  data-contentful-entry-id=\"abc123\"\n  data-contentful-field-id=\"title\"\n&gt;\n  Welcome to Our Store\n&lt;\/h1&gt;\n\n&lt;!-- Inspector Mode overlay model: --&gt;<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  A[\"DOM Element&lt;br\/&gt;&amp;lt;h1&amp;gt; #quot;Welcome to Our Store#quot;\"]:::node\n  A --&gt;|\"Has data-contentful-entry-id&lt;br\/&gt;and data-contentful-field-id\"| B[Live Preview SDK]:::sdk\n  B --&gt;|On hover| C[\"Blue overlay with field name&lt;br\/&gt;(e.g. #quot;title#quot;)\"]:::overlay\n  B --&gt;|On click| D[\"CMS sidebar navigates&lt;br\/&gt;to matching entry &amp; field\"]:::cms\n\n  classDef node fill:#ffffff,stroke:#555,stroke-width:1px;\n  classDef sdk fill:#f0f5ff,stroke:#2276c9,stroke-width:1px;\n  classDef overlay fill:#e0f3ff,stroke:#2276c9,stroke-width:1px;\n  classDef cms fill:#f5f5f5,stroke:#777,stroke-width:1px;<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The application\u2019s responsibility is to ensure those attributes are present in the right places. The SDK does the rest.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">2. Live Updates (Real-Time Content Sync)<\/h4>\n\n\n\n<p class=\"wp-block-paragraph\">When an editor changes a field in the CMS sidebar, Contentful sends a <code>postMessage<\/code> into the preview iframe containing the updated entry payload. The SDK receives this message and updates the app\u2019s data in real time.<\/p>\n\n\n\n<pre class=\"mermaid\">sequenceDiagram\n  participant E as CMS Sidebar\n  participant F as Preview Frame \/ Your Nuxt App\n  participant S as Live Preview SDK\n  participant V as Vue Reactive State\n  participant H as DOM h1\n\n  E-&gt;&gt;E: Editor changes title&lt;br\/&gt;to \"New Title\"\n  E--&gt;&gt;F: postMessage { field: \"title\",&lt;br\/&gt;value: \"New Title\" }\n  F-&gt;&gt;S: Message received\n  S-&gt;&gt;V: Update reactive data.title = \"New Title\"\n  V-&gt;&gt;H: Re-render h1 with \"New Title\"\n  note over H: No page reload.&lt;br\/&gt;Sub-second update.<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The key is making those SDK-driven updates land in the same reactive state the SSR pipeline uses.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The SSR Integration Challenge<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Nuxt SSR adds constraints that you cannot ignore if you want reliable, flicker-free live previews.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Problem 1: Preview vs. Published API<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Live Preview must pull from the <strong>Preview API<\/strong> (draft+published) instead of the <strong>Delivery API<\/strong> (published only). The GraphQL client needs to switch based on a runtime flag:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  subgraph PublishedModeproduct[\"Published Mode (production)\"]\n    A[Apollo Client] --&gt; B[Contentful Delivery API]\n    B --&gt; C[Only published content]\n    C --&gt; D[Aggressive caching]\n  end\n\n  subgraph PreviewModeeditorpre[\"Preview Mode (editor preview)\"]\n    E[Apollo Client] --&gt; F[Contentful Preview API]\n    F --&gt; G[Draft + published content]\n    G --&gt; H[\"No caching&lt;br\/&gt;(content changes constantly)\"]\n  end\n\n  I[\"Runtime flag:&lt;br\/&gt;contentful.preview\"] --&gt; A\n  I --&gt; E\n  I --&gt; D\n  I --&gt; H<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">A single runtime flag like <code>contentful.preview<\/code> should:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Point the GraphQL client at the Preview API<\/li>\n<li>Turn off all caching across:<\/li>\n<li>Apollo<\/li>\n<li>Any GraphQL server<\/li>\n<li>Page-level or HTTP caches<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">If any cache remains enabled in preview mode, \u201clive\u201d updates will lag or appear broken.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Problem 2: SSR State Must Match Client State<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Nuxt\u2019s SSR flow:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Server renders HTML using preview data.<\/li>\n<li>HTML is sent to the browser.<\/li>\n<li>Client hydrates that HTML into a live Vue app.<\/li>\n<\/ol>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  A[Request arrives] --&gt; B[\"SSR data fetch&lt;br\/&gt;via useAsyncData&lt;br\/&gt;(Preview\/Delivery API)\"]\n  B --&gt; C[Server-side render HTML]\n  C --&gt; D[HTML sent to browser]\n  D --&gt; E[\"Client-side hydration&lt;br\/&gt;rehydrate Vue app&lt;br\/&gt;from SSR state\"]\n  E --&gt; F[\"Live Preview SDK&lt;br\/&gt;patches useAsyncData cache\"]\n  F --&gt; G[\"Vue reactivity&lt;br\/&gt;updates DOM\"]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">When the Live Preview SDK pushes updates, it must update <strong>the same reactive state<\/strong> used during SSR. If the client uses a different data path, Vue will:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Throw hydration errors, or<\/li>\n<li>Silently diverge (the worst outcome: subtle bugs)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">A robust pattern:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Fetch data via <code>useAsyncData<\/code> in both SSR and client.<\/li>\n<li>Wire the SDK into a <strong>client-only plugin<\/strong> that:<\/li>\n<li>Subscribes to Live Preview messages.<\/li>\n<li>Patches the <code>useAsyncData<\/code> cache directly.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Vue\u2019s reactivity then re-renders the view without disrupting the SSR \u2192 hydration pipeline.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Problem 3: Data Attribute Propagation for Inspector Mode<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Inspector Mode depends on CMS metadata making it all the way to the DOM. That requires an explicit path from the CMS model through components to the HTML element.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Introduce a small <strong>CMS wrapper component<\/strong> to centralize this:<\/p>\n\n\n\n<pre><code class=\"language-vue\">&lt;!-- Component receives CMS data --&gt;\n&lt;Section :model=\"section\"&gt;\n  &lt;!-- model.sys.id = \"abc123\" --&gt;\n  &lt;!-- model.title = \"Welcome\" --&gt;\n&lt;\/Section&gt;\n\n&lt;!-- Wrapper adds data attributes --&gt;\n&lt;div\n  data-contentful-entry-id=\"abc123\"\n  data-contentful-field-id=\"title\"\n&gt;\n  {{ model.title }}\n&lt;\/div&gt;<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  A[\"CMS Entry&lt;br\/&gt;sys.id = abc123&lt;br\/&gt;title = #quot;Welcome#quot;\"] --&gt; B[\"Section component&lt;br\/&gt;props: model\"]\n  B --&gt; C[\"CMS wrapper&lt;br\/&gt;adds data-contentful-*\"]\n  C --&gt; D[\"DOM element&lt;br\/&gt;&amp;lt;div data-contentful-entry-id=#quot;abc123#quot;&lt;br\/&gt;data-contentful-field-id=#quot;title#quot;&amp;gt;Welcome&amp;lt;\/div&amp;gt;\"]\n\n  classDef default fill:#ffffff,stroke:#555,stroke-width:1px;<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Instead of sprinkling <code>data-contentful-*<\/code> attributes throughout the component tree, the wrapper guarantees:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Every CMS-backed field can be wired into Inspector Mode.<\/li>\n<li>The mapping between DOM nodes and CMS fields remains consistent.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Retrofitting this after the fact is painful; design for it early.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Problem 4: Rich Text Live Updates<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Contentful rich text fields are deeply nested JSON trees with embedded entries and assets. When an editor edits a paragraph, the SDK sends the <strong>full updated rich text JSON<\/strong>.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The simplest and most reliable pattern:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Treat the rich text field as an <strong>atomic value<\/strong>.<\/li>\n<li>When an update arrives, replace the entire rich text value for that component.<\/li>\n<li>Use a fully reactive renderer that accepts rich text JSON as a prop and re-renders when the prop changes.<\/li>\n<\/ul>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  A[\"CMS rich text field&lt;br\/&gt;JSON tree\"] --&gt; B[\"Nuxt data store&lt;br\/&gt;(atomic value)\"]\n  B --&gt; C[\"RichTextRenderer component&lt;br\/&gt;prop: content\"]\n  C --&gt; D[Rendered HTML]\n\n  E[\"Live Preview SDK update&lt;br\/&gt;full rich text JSON\"] --&gt; B\n  B --&gt;|reactive change| C<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Attempting fine-grained tree diffs or node-level patching tends to be brittle and unnecessary; full re-renders of rich text are usually cheap enough.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">DevTools: Closing the Loop for Developers<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Live Preview is not just for editors. A custom <strong>Contentful DevTools tab<\/strong> turns it into a powerful developer tool:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph DevToolsContentfulTa[\"DevTools \u2014 Contentful Tab\"]\n    A[\"Status:&lt;br\/&gt;\u25cf Connected to Live Preview\"]\n    B[\"Current Page&lt;br\/&gt;Entry ID: abc123&lt;br\/&gt;Content Type: Page&lt;br\/&gt;Open in Contentful \u2197\"]\n    C[\"Sections on this page&lt;br\/&gt;HeroSection (def456) Edit \u2197&lt;br\/&gt;FeatureGrid (ghi789) Edit \u2197&lt;br\/&gt;FAQ (jkl012) Edit \u2197\"]\n    D[\"Live Preview Events&lt;br\/&gt;12:34:56 title updated \u2192 #quot;New Title#quot;&lt;br\/&gt;12:34:58 heroImage updated \u2192 asset:xyz\"]\n  end<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This view gives:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A mapping from the currently rendered page to its CMS entries.<\/li>\n<li>One-click navigation to edit any section in Contentful.<\/li>\n<li>A real-time log of Live Preview events (including timing and payloads), invaluable for debugging data flows and performance.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Lessons from Live Preview Integration<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>The three-level iframe hierarchy is the first thing you will debug.<\/strong>  <\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">  Handle both <code>window.parent<\/code> and <code>window.top<\/code> from day one, or expect mysterious <code>postMessage<\/code> failures.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Preview mode must disable all caches.<\/strong>  <\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">  Live preview means \u201ccontent can change on every keystroke.\u201d Any caching layer will eventually serve stale data and ruin the experience.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Data attributes for Inspector Mode need architectural support.<\/strong>  <\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">  A wrapper or HOC that guarantees <code>data-contentful-entry-id<\/code>\/<code>field-id<\/code> reach the DOM is vastly better than ad-hoc attribute sprinkling.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Live Preview is a developer experience feature, too.<\/strong>  <\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">  Instant feedback accelerates component implementation and debugging. DevTools integration amplifies that effect.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">With robust live preview in place, editors can see exactly what they\u2019re building. The next step is giving them nuanced <strong>control over when content appears<\/strong> \u2014 without asking developers for another deployment.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Content-Driven Conditional Rendering: The Evaluator Plugin System<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Editors need to control not just <strong>what<\/strong> appears on a page, but <strong>whether<\/strong> it appears at all:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Campaign banners that show only during a specified date range.<\/li>\n<li>Sections that are visible only in certain regions or routes.<\/li>\n<li>Pricing variants that target specific A\/B test buckets or feature flags.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Hardcoding these rules in application logic leads to a familiar pattern: every change requires a code change, PR, review, and deployment.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The goal is different:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">> Allow editors to define <strong>conditions<\/strong> on CMS sections, have the application evaluate them at render time (SSR and client), and support new condition types without shipping new code.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The solution is a <strong>plugin-based evaluator architecture<\/strong>.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Architecture: Evaluator Plugins<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Each condition type is implemented as its own <strong>evaluator plugin<\/strong>. The CMS content model adds a generic <code>condition<\/code> field to every section. At render time, the application loads the appropriate evaluator and checks the condition before deciding whether to render.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  subgraph CMS\n    A[\"Section: #quot;Summer Promotion Banner#quot;&lt;br\/&gt;&lt;br\/&gt;Content:&lt;br\/&gt;title: #quot;Summer Sale \u2014 20% Off#quot;&lt;br\/&gt;image: hero-summer.jpg&lt;br\/&gt;&lt;br\/&gt;Condition:&lt;br\/&gt;type: #quot;dateRange#quot;&lt;br\/&gt;config:&lt;br\/&gt;startDate: #quot;2025-06-01#quot;&lt;br\/&gt;endDate: #quot;2025-08-31#quot;&lt;br\/&gt;&lt;br\/&gt;Visible only if:&lt;br\/&gt;current date is within range\"]\n  end\n\n  A --&gt; B[Application at render time]\n\n  B --&gt; C[Load section from CMS]\n  C --&gt; D[\"Read condition&lt;br\/&gt;{ type: #quot;dateRange#quot;, config: {...} }\"]\n  D --&gt; E[\"Find evaluator plugin:&lt;br\/&gt;dateRangeEvaluator\"]\n  E --&gt; F[\"Execute evaluate(config, context)\"]\n  F --&gt; G{Result?}\n  G --&gt;|true| H[Render section]\n  G --&gt;|false| I[Skip section entirely]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This pattern mirrors how you might structure the Live Preview SDK integration:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>A central runtime that knows <strong>what<\/strong> is needed (condition type).<\/li>\n<li>A registry that knows <strong>how<\/strong> to load the right plugin.<\/li>\n<li>Plugins that encapsulate behavior and can evolve independently.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h3 class=\"wp-block-heading\">The Evaluator Interface<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Every evaluator plugin implements a simple contract:<\/p>\n\n\n\n<pre><code class=\"language-ts\">Evaluator Plugin Interface:\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 interface ConditionEvaluator {                  \u2502\n\u2502   type: string                   \/\/ e.g. \"dateRange\"\n\u2502   evaluate(                                      \u2502\n\u2502     config: Record&lt;string, any&gt;, \/\/ CMS-defined params\n\u2502     context: EvaluatorContext    \/\/ request context\n\u2502   ): boolean | Promise&lt;boolean&gt;                  \u2502\n\u2502 }                                                \u2502\n\u2502                                                  \u2502\n\u2502 interface EvaluatorContext {                     \u2502\n\u2502   route: RouteLocationNormalized                 \u2502\n\u2502   cookies: Record&lt;string, string&gt;                \u2502\n\u2502   query: Record&lt;string, string&gt;                  \u2502\n\u2502   userAgent: string                              \u2502\n\u2502   isSSR: boolean                                 \u2502\n\u2502   abTests: Record&lt;string, string&gt;                \u2502\n\u2502 }                                                \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  A[\"ConditionEvaluator&lt;br\/&gt;type: string&lt;br\/&gt;evaluate(config, context)\"] --&gt; B[\"config&lt;br\/&gt;CMS-defined params\"]\n  A --&gt; C[\"context&lt;br\/&gt;EvaluatorContext\"]\n\n  C --&gt; D[route]\n  C --&gt; E[cookies]\n  C --&gt; F[query]\n  C --&gt; G[userAgent]\n  C --&gt; H[isSSR]\n  C --&gt; I[abTests]\n\n  B --&gt; J{evaluate&lt;br\/&gt;returns}\n  C --&gt; J\n  J --&gt;|\"boolean&lt;br\/&gt;or Promise&lt;boolean&gt;\"| K[Render decision]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>context<\/code> object exposes request-level details:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Route (for path and params)<\/li>\n<li>Cookies (for personalization and persisted state)<\/li>\n<li>Query parameters (for campaign links, tracking, etc.)<\/li>\n<li>User agent (for device\/agent segmentation)<\/li>\n<li>SSR flag (to distinguish server vs client behavior)<\/li>\n<li>A\/B test assignments (for experiment targeting)<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This gives immense flexibility without tying conditions to specific application modules.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Built-In Evaluator Types<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A typical system ships with a set of core evaluators:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Evaluator<\/th><th>Condition<\/th><th>Example Config<\/th><\/tr><\/thead><tbody><tr><td><code>dateRange<\/code><\/td><td>Section visible during a date range<\/td><td><code>{ start: \"2025-06-01\", end: \"2025-08-31\" }<\/code><\/td><\/tr><tr><td><code>abTest<\/code><\/td><td>Section visible for a specific A\/B test variant<\/td><td><code>{ testId: \"pricing\", variant: \"B\" }<\/code><\/td><\/tr><tr><td><code>route<\/code><\/td><td>Section visible on specific URL patterns<\/td><td><code>{ patterns: [\"\/products\/*\", \"\/offers\"] }<\/code><\/td><\/tr><tr><td><code>cookie<\/code><\/td><td>Section visible when a cookie has a specific value<\/td><td><code>{ name: \"returning\", value: \"true\" }<\/code><\/td><\/tr><tr><td><code>queryParam<\/code><\/td><td>Section visible when a URL parameter is present<\/td><td><code>{ param: \"promo\", value: \"summer\" }<\/code><\/td><\/tr><tr><td><code>composite<\/code><\/td><td>Combines multiple conditions with AND\/OR logic<\/td><td><code>{ operator: \"and\", conditions: [...] }<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>composite<\/code> evaluator is where the power really emerges.<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  A[\"Composite Condition&lt;br\/&gt;type: #quot;composite#quot;&lt;br\/&gt;operator: #quot;and#quot;\"] --&gt; B[\"Condition 1:&lt;br\/&gt;dateRange&lt;br\/&gt;2025-06-01 \u2192 2025-08-31\"]\n  A --&gt; C[\"Condition 2:&lt;br\/&gt;abTest #quot;pricing#quot; variant #quot;B#quot;\"]\n  A --&gt; D[\"Condition 3:&lt;br\/&gt;route patterns: \/checkout\/*&lt;br\/&gt;negate: true\"]\n\n  B --&gt; E{All true?}\n  C --&gt; E\n  D --&gt; E\n\n  E --&gt;|true| F[Section visible]\n  E --&gt;|false| G[Section hidden]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Editors compose complex logic from simple building blocks \u2014 no code changes, no deployments.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Dynamic Plugin Loading<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Evaluator plugins are loaded <strong>on demand<\/strong>. If a section has a <code>\"dateRange\"<\/code> condition, only the <code>dateRange<\/code> evaluator is ever imported:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  A[\"Section condition&lt;br\/&gt;{ type: #quot;dateRange#quot;, ... }\"] --&gt; B[Plugin Registry]\n\n  subgraph Registry\n    B --&gt; C[\"'dateRange' \u2192 lazy import evaluators\/dateRange\"]\n    B --&gt; D[\"'abTest' \u2192 lazy import evaluators\/abTest\"]\n    B --&gt; E[\"'route' \u2192 lazy import evaluators\/route\"]\n  end\n\n  C --&gt; F[\"Load dateRange evaluator&lt;br\/&gt;into bundle\"]\n  D -. not used .- X[\"(Not loaded)\"]\n  E -. not used .- Y[\"(Not loaded)\"]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This mirrors how you might isolate heavy Live Preview logic to preview-only builds or routes. Dynamic imports ensure:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Production bundles stay lean.<\/li>\n<li>Pages only pay for the condition types they actually use.<\/li>\n<li>Heavy dependencies (e.g. external SDKs for feature flags) are kept off most pages.<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Async Evaluators<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Some conditions need to talk to external systems:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Feature flag services<\/li>\n<li>Experiment platforms<\/li>\n<li>Geo-IP resolvers<\/li>\n<li>Permissions or entitlement services<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The evaluator interface supports async evaluation by allowing <code>Promise<boolean><\/code>:<\/p>\n\n\n\n<pre><code class=\"language-ts\">Async Evaluator Example: Feature Flag\n\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 evaluator: \"featureFlag\"                        \u2502\n\u2502 config: { flag: \"new-checkout-flow\" }           \u2502\n\u2502                                                \u2502\n\u2502 async evaluate(config, context) {              \u2502\n\u2502   const flags = await fetchFeatureFlags()      \u2502\n\u2502   return flags[config.flag] === true           \u2502\n\u2502 }                                              \u2502\n\u2502                                                \u2502\n\u2502 This evaluator fetches feature flags from an   \u2502\n\u2502 external service and checks if the specified   \u2502\n\u2502 flag is enabled. The result is cached for the  \u2502\n\u2502 duration of the SSR request.                   \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">sequenceDiagram\n  participant R as Request\n  participant Eval as featureFlag evaluator\n  participant FF as Feature Flag Service\n  participant SSR as SSR Renderer\n\n  R-&gt;&gt;Eval: evaluate(config, context)\n  Eval-&gt;&gt;FF: fetchFeatureFlags()\n  FF--&gt;&gt;Eval: flags payload\n  Eval--&gt;&gt;SSR: boolean result&lt;br\/&gt;(flag enabled?)\n  SSR-&gt;&gt;SSR: Cache result for&lt;br\/&gt;duration of SSR request<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Key behavior:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>During SSR<\/strong>, async evaluators are awaited so the server sends HTML matching the current feature state.<\/li>\n<li><strong>During hydration<\/strong>, results are hydrated from SSR payloads; no additional round-trip or re-evaluation is required.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This lines up cleanly with the Live Preview strategy: both systems rely on SSR as the single source of truth for initial HTML, then reuse that state on the client.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Adding a New Condition Type<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The plugin pattern shines when you want to extend behavior without touching the core rendering pipeline.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Adding a new evaluator is a three-step process:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Create the evaluator file<\/strong> \u2014 implement the <code>ConditionEvaluator<\/code> interface.<\/li>\n<li><strong>Register it in the plugin registry<\/strong> \u2014 add a lazy import entry.<\/li>\n<li><strong>Expose the condition type in the CMS model<\/strong> \u2014 so editors can use it.<\/li>\n<\/ol>\n\n\n\n<pre><code class=\"language-ts\">Adding a new evaluator:\n\nStep 1: Create evaluator\n  evaluators\/deviceType.ts\n  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n  \u2502 export const deviceTypeEvaluator = {   \u2502\n  \u2502   type: 'deviceType',                  \u2502\n  \u2502   evaluate(config, context) {          \u2502\n  \u2502     const isMobile = \/Mobile\/i.test(   \u2502\n  \u2502       context.userAgent                \u2502\n  \u2502     )                                  \u2502\n  \u2502     return config.device === 'mobile'  \u2502\n  \u2502       ? isMobile                       \u2502\n  \u2502       : !isMobile                      \u2502\n  \u2502   }                                    \u2502\n  \u2502 }                                      \u2502\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\nStep 2: Register\n  registry.ts\n  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n  \u2502 'deviceType': lazy(() =&gt;              \u2502\n  \u2502   import('.\/evaluators\/deviceType'))  \u2502\n  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\nStep 3: CMS content model\n  Add \"deviceType\" to the condition type enum\n  Content editors can now create device-specific sections<\/code><\/pre>\n\n\n\n<pre class=\"mermaid\">flowchart LR\n  A[\"Step 1:&lt;br\/&gt;Create deviceType evaluator&lt;br\/&gt;evaluators\/deviceType.ts\"] --&gt; B[\"Step 2:&lt;br\/&gt;Register in registry.ts&lt;br\/&gt;'deviceType': lazy(() =&gt; import(...))\"]\n  B --&gt; C[\"Step 3:&lt;br\/&gt;Update CMS model&lt;br\/&gt;add #quot;deviceType#quot; to enum\"]\n  C --&gt; D[\"Editors can configure&lt;br\/&gt;device-specific sections\"]<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">No changes to the section renderer, no framework rewiring, no deployment choreography beyond shipping the plugin and updating the CMS model.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">The Rendering Pipeline<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Section rendering now becomes a simple, declarative pipeline:<\/p>\n\n\n\n<pre class=\"mermaid\">flowchart TB\n  A[\"CMS Page&lt;br\/&gt;sections: S1..S5\"] --&gt; B[For each section]\n\n  B --&gt; C[S1: No condition]\n  C --&gt; C1[Result: RENDER]\n\n  B --&gt; D[S2: dateRange]\n  D --&gt; D1[\"Evaluate \u2192 true\"]\n  D1 --&gt; D2[Result: RENDER]\n\n  B --&gt; E[\"S3: abTest(pricing)\"]\n  E --&gt; E1[\"Evaluate \u2192 false\"]\n  E1 --&gt; E2[Result: SKIP]\n\n  B --&gt; F[\"S4: composite(AND)\"]\n  F --&gt; F1[\"Evaluate \u2192 true\"]\n  F1 --&gt; F2[Result: RENDER]\n\n  B --&gt; G[S5: dateRange]\n  G --&gt; G1[\"Evaluate \u2192 false\"]\n  G1 --&gt; G2[Result: SKIP]\n\n  C1 --&gt; H[\"Rendered page&lt;br\/&gt;S1, S2, S4\"]\n  D2 --&gt; H\n  F2 --&gt; H<\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sections that fail their conditions are <strong>completely omitted<\/strong> from the HTML:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>No hidden DOM.<\/li>\n<li>No client-side toggling.<\/li>\n<li>No wasted rendering time or CLS risk.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">This is especially powerful when combined with Live Preview: editors can see in real time whether a condition hides or reveals a section, and the system maintains SSR performance and correctness.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Lessons from the Evaluator System<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>The plugin pattern is the right abstraction for open-ended condition types.<\/strong>  <\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">  A fixed set of conditions becomes restrictive quickly. Plugins let you extend behavior without touching core rendering logic.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Dynamic loading prevents evaluator code bloat.<\/strong>  <\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">  Loading every evaluator upfront makes every page carry every possible condition implementation. Dynamic imports keep bundles slim, which matters even more when some evaluators depend on heavy SDKs.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>The composite evaluator covers most complex conditions.<\/strong>  <\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">  Most \u201cadvanced\u201d conditions are just compositions of simpler ones: \u201cdate range AND A\/B variant AND URL pattern.\u201d Only truly new logic requires new plugins.<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Content editors should not need to understand the evaluator system.<\/strong>  <\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">  The CMS UI should present a simple configuration experience: pick a condition type, fill in fields, publish. The plugin machinery stays behind the curtain.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">Bringing It All Together<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">When <strong>Live Preview<\/strong> is combined with a <strong>content-driven conditional rendering system<\/strong>, the result is a powerful, editor-centric platform suitable for large enterprise applications:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Editors can:<\/li>\n<li>See every change in real time, in the actual Nuxt SSR app.<\/li>\n<li>Configure complex visibility rules without developer involvement.<\/li>\n<li>Validate that conditions behave as expected via live previews.<\/li>\n<\/ul>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Developers can:<\/li>\n<li>Maintain a clean SSR data flow with consistent reactive state.<\/li>\n<li>Avoid cache pitfalls in preview mode via a single <code>preview<\/code> flag.<\/li>\n<li>Extend behavior by adding small, testable plugins rather than touching core systems.<\/li>\n<li>Use DevTools to observe Live Preview events and condition outcomes in detail.<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">The result is a CMS integration that respects both <strong>editor velocity<\/strong> and <strong>engineering rigor<\/strong> \u2014 a foundation that carries well into e-commerce, SaaS dashboards, content sites, and other complex frontend applications.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Modern CMS-driven frontends live in a constant tension: editors want instant visual feedback and full control over what appears, when, and to whom, while developers need SSR correctness, bundle discipline, and maintainable architectures. This masterclass walks through two complementary patterns that resolve that tension on a Nuxt 4 + Contentful stack: Real-time WYSIWYG editing with [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":228,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[5],"tags":[],"class_list":["post-17","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-advanced-web-app-with-nuxt-and-net"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.7 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Advanced CMS Patterns: Conditional Rendering &amp; Live Preview - Software Production<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/softwareproduction.eu\/?p=17\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Advanced CMS Patterns: Conditional Rendering &amp; Live Preview - Software Production\" \/>\n<meta property=\"og:description\" content=\"Modern CMS-driven frontends live in a constant tension: editors want instant visual feedback and full control over what appears, when, and to whom, while developers need SSR correctness, bundle discipline, and maintainable architectures. This masterclass walks through two complementary patterns that resolve that tension on a Nuxt 4 + Contentful stack: Real-time WYSIWYG editing with [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/softwareproduction.eu\/?p=17\" \/>\n<meta property=\"og:site_name\" content=\"Software Production\" \/>\n<meta property=\"article:published_time\" content=\"2026-06-06T21:48:34+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-06-07T01:21:55+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/10-advanced-cms-patterns.jpg\" \/>\n\t<meta property=\"og:image:width\" content=\"1880\" \/>\n\t<meta property=\"og:image:height\" content=\"1253\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"Munir Husseini\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Munir Husseini\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"18 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=17#article\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=17\"},\"author\":{\"name\":\"Munir Husseini\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/person\\\/fec48f54713e1bd117640fb9b748802f\"},\"headline\":\"Advanced CMS Patterns: Conditional Rendering &#038; Live Preview\",\"datePublished\":\"2026-06-06T21:48:34+00:00\",\"dateModified\":\"2026-06-07T01:21:55+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=17\"},\"wordCount\":1925,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=17#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/10-advanced-cms-patterns.jpg\",\"articleSection\":[\"Advanced Web App with Nuxt and .NET\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\\\/\\\/softwareproduction.eu\\\/?p=17#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=17\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=17\",\"name\":\"Advanced CMS Patterns: Conditional Rendering & Live Preview - Software Production\",\"isPartOf\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=17#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=17#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/10-advanced-cms-patterns.jpg\",\"datePublished\":\"2026-06-06T21:48:34+00:00\",\"dateModified\":\"2026-06-07T01:21:55+00:00\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=17#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/softwareproduction.eu\\\/?p=17\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=17#primaryimage\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/10-advanced-cms-patterns.jpg\",\"contentUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/06\\\/10-advanced-cms-patterns.jpg\",\"width\":1880,\"height\":1253},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/?p=17#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\\\/\\\/softwareproduction.eu\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Advanced CMS Patterns: Conditional Rendering &#038; Live Preview\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#website\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/\",\"name\":\"Softwareproduction\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\\\/\\\/softwareproduction.eu\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#organization\",\"name\":\"Munir Husseini\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/softwareproduction-logo-32.png\",\"contentUrl\":\"https:\\\/\\\/softwareproduction.eu\\\/wordpress\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/softwareproduction-logo-32.png\",\"width\":32,\"height\":32,\"caption\":\"Munir Husseini\"},\"image\":{\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/logo\\\/image\\\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\\\/\\\/softwareproduction.eu\\\/#\\\/schema\\\/person\\\/fec48f54713e1bd117640fb9b748802f\",\"name\":\"Munir Husseini\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"url\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"contentUrl\":\"https:\\\/\\\/secure.gravatar.com\\\/avatar\\\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g\",\"caption\":\"Munir Husseini\"},\"sameAs\":[\"https:\\\/\\\/softwareproduction.eu\\\/\"],\"url\":\"https:\\\/\\\/softwareproduction.eu\\\/?author=1\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Advanced CMS Patterns: Conditional Rendering & Live Preview - Software Production","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/softwareproduction.eu\/?p=17","og_locale":"en_US","og_type":"article","og_title":"Advanced CMS Patterns: Conditional Rendering & Live Preview - Software Production","og_description":"Modern CMS-driven frontends live in a constant tension: editors want instant visual feedback and full control over what appears, when, and to whom, while developers need SSR correctness, bundle discipline, and maintainable architectures. This masterclass walks through two complementary patterns that resolve that tension on a Nuxt 4 + Contentful stack: Real-time WYSIWYG editing with [&hellip;]","og_url":"https:\/\/softwareproduction.eu\/?p=17","og_site_name":"Software Production","article_published_time":"2026-06-06T21:48:34+00:00","article_modified_time":"2026-06-07T01:21:55+00:00","og_image":[{"width":1880,"height":1253,"url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/10-advanced-cms-patterns.jpg","type":"image\/jpeg"}],"author":"Munir Husseini","twitter_card":"summary_large_image","twitter_misc":{"Written by":"Munir Husseini","Est. reading time":"18 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/softwareproduction.eu\/?p=17#article","isPartOf":{"@id":"https:\/\/softwareproduction.eu\/?p=17"},"author":{"name":"Munir Husseini","@id":"https:\/\/softwareproduction.eu\/#\/schema\/person\/fec48f54713e1bd117640fb9b748802f"},"headline":"Advanced CMS Patterns: Conditional Rendering &#038; Live Preview","datePublished":"2026-06-06T21:48:34+00:00","dateModified":"2026-06-07T01:21:55+00:00","mainEntityOfPage":{"@id":"https:\/\/softwareproduction.eu\/?p=17"},"wordCount":1925,"commentCount":0,"publisher":{"@id":"https:\/\/softwareproduction.eu\/#organization"},"image":{"@id":"https:\/\/softwareproduction.eu\/?p=17#primaryimage"},"thumbnailUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/10-advanced-cms-patterns.jpg","articleSection":["Advanced Web App with Nuxt and .NET"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/softwareproduction.eu\/?p=17#respond"]}]},{"@type":"WebPage","@id":"https:\/\/softwareproduction.eu\/?p=17","url":"https:\/\/softwareproduction.eu\/?p=17","name":"Advanced CMS Patterns: Conditional Rendering & Live Preview - Software Production","isPartOf":{"@id":"https:\/\/softwareproduction.eu\/#website"},"primaryImageOfPage":{"@id":"https:\/\/softwareproduction.eu\/?p=17#primaryimage"},"image":{"@id":"https:\/\/softwareproduction.eu\/?p=17#primaryimage"},"thumbnailUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/10-advanced-cms-patterns.jpg","datePublished":"2026-06-06T21:48:34+00:00","dateModified":"2026-06-07T01:21:55+00:00","breadcrumb":{"@id":"https:\/\/softwareproduction.eu\/?p=17#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/softwareproduction.eu\/?p=17"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/softwareproduction.eu\/?p=17#primaryimage","url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/10-advanced-cms-patterns.jpg","contentUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/10-advanced-cms-patterns.jpg","width":1880,"height":1253},{"@type":"BreadcrumbList","@id":"https:\/\/softwareproduction.eu\/?p=17#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/softwareproduction.eu\/"},{"@type":"ListItem","position":2,"name":"Advanced CMS Patterns: Conditional Rendering &#038; Live Preview"}]},{"@type":"WebSite","@id":"https:\/\/softwareproduction.eu\/#website","url":"https:\/\/softwareproduction.eu\/","name":"Softwareproduction","description":"","publisher":{"@id":"https:\/\/softwareproduction.eu\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/softwareproduction.eu\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/softwareproduction.eu\/#organization","name":"Munir Husseini","url":"https:\/\/softwareproduction.eu\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/softwareproduction.eu\/#\/schema\/logo\/image\/","url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/05\/softwareproduction-logo-32.png","contentUrl":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/05\/softwareproduction-logo-32.png","width":32,"height":32,"caption":"Munir Husseini"},"image":{"@id":"https:\/\/softwareproduction.eu\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/softwareproduction.eu\/#\/schema\/person\/fec48f54713e1bd117640fb9b748802f","name":"Munir Husseini","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/b07845732d4d7bddfc43e608ae6662d564a14b35706dfae0c9610071d978f54e?s=96&d=mm&r=g","caption":"Munir Husseini"},"sameAs":["https:\/\/softwareproduction.eu\/"],"url":"https:\/\/softwareproduction.eu\/?author=1"}]}},"jetpack_featured_media_url":"https:\/\/softwareproduction.eu\/wordpress\/wp-content\/uploads\/2026\/06\/10-advanced-cms-patterns.jpg","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/17","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=17"}],"version-history":[{"count":11,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/17\/revisions"}],"predecessor-version":[{"id":229,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/posts\/17\/revisions\/229"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=\/wp\/v2\/media\/228"}],"wp:attachment":[{"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=17"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=17"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/softwareproduction.eu\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=17"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}