Shopify Schema-Driven Architecture

Liquid, CMS, Caching & Hydrogen Deep Dive

Liquid Templating Engine

Server-Side Rendering Process:

<!-- sections/hero.liquid -->
<section class="hero" style="background: {{ section.settings.bg_color }}">
  <img src="{{ section.settings.image | img_url: '1920x' }}"
       alt="{{ section.settings.image.alt }}">
  <h1 style="color: {{ section.settings.text_color }}">
    {{ section.settings.heading }}
  </h1>
</section>

{% schema %}
{
  "name": "Hero Section",
  "settings": [
    {"id": "heading", "type": "text", "label": "Heading"},
    {"id": "image", "type": "image_picker", "label": "Image"},
    {"id": "bg_color", "type": "color", "default": "#ffffff"},
    {"id": "text_color", "type": "color", "default": "#000000"}
  ]
}
{% endschema %}

Key Points: Schema → Theme Editor UI → Settings Object → Liquid Variables

CMS Integration & Data Flow

Theme Editor → Settings Storage:

// Stored in shop's theme settings
{
  "sections": {
    "hero-banner": {
      "type": "hero",
      "settings": {
        "heading": "Welcome to Our Store",
        "bg_color": "#f8f9fa",
        "text_color": "#333333",
        "image": "products/hero-image.jpg"
      }
    }
  }
}

Liquid Access Pattern:

{{ section.settings.heading }}        // "Welcome to Our Store"
{{ section.settings.bg_color }}       // "#f8f9fa"
{{ section.settings.image.url }}      // Full image URL

Flow: CMS Edit → JSON Store → Server Render → HTML Output

User Configs Drive Styles & Logic

CSS Custom Properties Pattern:

<style>
  .hero-{{ section.id }} {
    --bg-color: {{ section.settings.bg_color }};
    --text-color: {{ section.settings.text_color }};
    --font-size: {{ section.settings.font_size }}px;
    background: var(--bg-color);
    color: var(--text-color);
  }

  {% if section.settings.enable_parallax %}
  .hero-{{ section.id }} {
    background-attachment: fixed;
  }
  {% endif %}
</style>

<section class="hero-{{ section.id }}">
  {% for block in section.blocks %}
    {% case block.type %}
      {% when 'heading' %}
        <h1>{{ block.settings.text }}</h1>
      {% when 'button' %}
        <a href="{{ block.settings.url }}">{{ block.settings.label }}</a>
    {% endcase %}
  {% endfor %}
</section>

Incremental Updates & JSON Diffs

Theme Editor Change Detection:

// Only changed settings sent to server
const changeset = {
  "sections.hero-banner.settings.heading": "New Heading Text",
  "sections.hero-banner.settings.bg_color": "#ff6b35",
};

// PATCH request with minimal payload
fetch("/admin/themes/123/settings.json", {
  method: "PATCH",
  body: JSON.stringify({ settings: changeset }),
});

Server Processing:

# Rails backend merges changes
current_settings.deep_merge!(changeset)
ThemeRenderer.invalidate_cache(section_id)

Benefits: Minimal bandwidth, faster saves, granular cache invalidation

Multi-Layer Caching

1. Theme Asset Caching:

{{ 'theme.css' | asset_url }}
// → https://cdn.shopify.com/s/files/1/theme.css?v=12345678

2. Section-Level Caching:

# Server-side fragment caching
cache_key = "section:#{section.id}:#{settings_hash}:#{product_ids_hash}"
Rails.cache.fetch(cache_key, expires_in: 1.hour) do
  render_section(section, settings, products)
end

3. CDN Edge Caching:

  • Static assets: Long-term cache (1 year)
  • Dynamic content: Short cache (5-15 min) with ESI
  • Geographic distribution via Shopify's CDN

4. Cache Invalidation:

# When settings change
invalidate_pattern("section:#{section_id}:*")

Hydrogen (React) Equivalent

Schema-Driven React Components:

// app/components/Hero.tsx
export function Hero({ heading, bgColor, textColor, image }: HeroProps) {
  return (
    <section
      className="hero"
      style={
        {
          "--bg-color": bgColor,
          "--text-color": textColor,
        } as CSSProperties
      }
    >
      <img src={image} alt="" />
      <h1>{heading}</h1>
    </section>
  );
}

// Schema definition for Hydrogen admin
Hero.schema = {
  name: "Hero Section",
  settings: [
    { id: "heading", type: "text", label: "Heading" },
    { id: "bgColor", type: "color", label: "Background" },
    { id: "textColor", type: "color", label: "Text Color" },
    { id: "image", type: "image_picker", label: "Image" },
  ],
};

Hydrogen: Data Flow & SSR

Server-Side Rendering with Streaming:

// app/routes/$.tsx - Catch-all route
export async function loader({ params, context }: LoaderArgs) {
  const { page } = await context.storefront.query(PAGE_QUERY, {
    variables: { handle: params["*"] },
  });

  return defer({
    page: await page,
    sections: page.sections, // Settings from Shopify admin
  });
}

export default function Page() {
  const { page, sections } = useLoaderData<typeof loader>();

  return (
    <Suspense fallback={<PageSkeleton />}>
      <Await resolve={sections}>
        {(resolvedSections) =>
          resolvedSections.map((section) => (
            <DynamicSection key={section.id} {...section} />
          ))
        }
      </Await>
    </Suspense>
  );
}

Hydrogen Caching & Performance

1. Oxygen Edge Caching:

// app/entry.server.tsx
export default function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext
) {
  // Hydrogen auto-manages cache headers
  return new Response(markup, {
    headers: {
      "Cache-Control": "public, max-age=300, stale-while-revalidate=3600",
      Vary: "Accept-Encoding",
    },
  });
}

2. GraphQL Caching:

// Automatic query caching with cache policies
const { page } = await storefront.query(PAGE_QUERY, {
  cache: storefront.CacheShort(), // 1 minute
  variables: { handle },
});

3. Sub-request Caching:

// Component-level caching
export function ProductGrid({ collection }: { collection: string }) {
  return (
    <Suspense fallback={<ProductGridSkeleton />}>
      <Await
        resolve={storefront.query(PRODUCTS_QUERY, {
          cache: storefront.CacheLong(), // 1 hour
          variables: { collection },
        })}
      >
        {(products) => <ProductList products={products} />}
      </Await>
    </Suspense>
  );
}

Architecture Patterns & Takeaways

Schema-Driven Design Benefits:

  • ✅ Separation of Concerns: Dev defines structure, merchants control content
  • ✅ Performance: Server-side rendering + multi-layer caching
  • ✅ DX/UX: One schema definition drives both code and admin UI
  • ✅ Incremental Updates: JSON diffs minimize data transfer

Implementation Patterns:

Aspect Liquid/Theme Hydrogen/React
Rendering Server-side only SSR + Client hydration
Caching Fragment + CDN Edge + GraphQL layers
Updates Full page reload Streaming + suspense
Schema JSON in comments JS objects

Apply To: CMSs, page builders, design systems, any user-configurable UI

Resources:

Slide 1: Title

Slide 2: Liquid Templating Engine

Slide 3: CMS Integration & Data Flow

Slide 4: Dynamic Styles & Configuration

Slide 5: JSON Diff & Change Detection

Slide 6: Caching Strategy

Slide 7: Hydrogen Architecture

Slide 8: Hydrogen Data Flow & SSR

Slide 9: Hydrogen Caching & Performance

Slide 10: Key Takeaways