Understanding Shopify Theme Development: Liquid for Next.js and React Developers

A concise mental model and practical guide to Shopify’s Liquid templating language, designed for frontend developers familiar with React or Next.js.

·Matija Žiberna·
Understanding Shopify Theme Development: Liquid for Next.js and React Developers

As someone who's spent most of my time building headless storefronts with frameworks like Next.js, I didn’t take Shopify theme development very seriously until recently.

The truth is that theme customization is much more common than headless builds. It is faster to ship, easier to hand off to clients, and more budget-friendly. I wanted to understand how Shopify’s native themes work and what it takes to customize or build one from scratch using Liquid.

This post breaks down the mental model that helped me get up to speed. If you're a frontend or full-stack developer coming from the React or Next.js world and trying to understand Shopify’s Liquid-based theming system, this is for you.


The 80/20 of Shopify Themes: Start Here

Look at the high-level structure. Shopify themes are made of templates, sections, blocks, snippets, and assets.

Once you see how these parts fit together, you know about 80 percent of the system.


Templates

  • Templates are top-level pages.
  • Each template defines the layout for one page type. This includes product pages, collection pages, cart, homepage, and others.
  • Examples: /templates/product.liquid, /templates/cart.liquid, /templates/index.liquid
  • Templates usually reference multiple sections.

Sections

  • Sections are modular content blocks that you can reuse and customize.
  • Some sections are static (hardcoded), others are dynamic (selectable and reorderable in the Theme Editor).
  • Each section lives in /sections/ and contains Liquid code with a {% schema %} block.
  • Example sections: main-product.liquid, featured-collection.liquid, testimonial-slider.liquid

Blocks

  • Blocks are repeatable components inside a section.
  • You can use them for things like testimonials, FAQ entries, or a grid of feature cards.
  • Blocks are defined inside the section's schema.
  • Merchants can add, remove, or reorder them.

Example:

{% for block in section.blocks %}
  <div class="testimonial">
    <p>{{ block.settings.quote }}</p>
    <p class="author">{{ block.settings.name }}</p>
  </div>
{% endfor %}

This is like how Payload CMS lets you define repeatable, flexible blocks.


Snippets

  • Snippets are reusable chunks of Liquid and HTML.
  • They work much like React components.
  • Snippets are stored in /snippets/ and included with {% render 'snippet-name' %} or {% include 'snippet-name' %}.
  • Snippets help keep code modular and clean.

Assets

  • These are your static files: CSS, JS, fonts, and images.
  • Assets are stored in the /assets/ folder.
  • Reference them in Liquid like this:
<link rel="stylesheet" href="{{ 'theme.css' | asset_url }}">
<script src="{{ 'app.js' | asset_url }}" defer></script>

You can pre-process CSS (for example, with Tailwind) or JavaScript (for example, with React) and upload the final files here.


How the {% schema %} Block Works (and Why It Matters)

In Shopify theme development, the {% schema %} block lets you turn static Liquid files into customizable components. Merchants can then edit these through the Theme Editor without touching code.

In simple terms: Schema lets you define settings and content blocks for the end user (the merchant) to control.

You can enable editing for things like:

  • Text fields (heading, subheading, button label)
  • Colors, spacing, alignment, and visibility
  • Repeating items (testimonials, FAQ entries, image galleries)

The schema is written in JSON and sits at the bottom of a .liquid file, inside {% schema %} tags.


Example: A Simple Schema Block for a Section

Say you want a "Text with Image" section. Here’s a simple schema:

{% schema %}
{
  "name": "Text with Image",
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Heading",
      "default": "Welcome to our store"
    },
    {
      "type": "textarea",
      "id": "text",
      "label": "Body text"
    },
    {
      "type": "image_picker",
      "id": "image",
      "label": "Image"
    }
  ]
}
{% endschema %}

This will:

  • Show fields for heading, text, and an image picker in the Shopify Theme Editor.
  • Let the merchant change content visually.

Access these values in your section like this:

<h2>{{ section.settings.heading }}</h2>
<p>{{ section.settings.text }}</p>
<img src="{{ section.settings.image | image_url }}" alt="">

Blocks in Schema: Repeatable Content

To let a merchant add multiple items, like testimonials, you define blocks in the schema.

{% schema %}
{
  "name": "Testimonials",
  "settings": [],
  "blocks": [
    {
      "type": "testimonial",
      "name": "Testimonial",
      "settings": [
        {
          "type": "text",
          "id": "quote",
          "label": "Quote"
        },
        {
          "type": "text",
          "id": "author",
          "label": "Author Name"
        }
      ]
    }
  ],
  "max_blocks": 5
}
{% endschema %}

Render blocks like this:

{% for block in section.blocks %}
  <blockquote>{{ block.settings.quote }}</blockquote>
  <cite>{{ block.settings.author }}</cite>
{% endfor %}

This flexibility is great for both developers and merchants.


Schema in Templates vs Sections

This part is important for anyone who thinks in component logic.

In a section, the {% schema %}:

  • Defines fields and blocks merchants can edit
  • Specifies which controls appear in the Theme Editor

In a template, you don’t add {% schema %} directly.

Modern Shopify themes, especially Online Store 2.0, use JSON for templates. JSON templates define:

  • Which sections appear on the page
  • Their order

Example: templates/index.json

{
  "sections": {
    "hero": {
      "type": "hero"
    },
    "rich-text": {
      "type": "rich-text"
    }
  },
  "order": ["hero", "rich-text"]
}

This lets merchants control the page structure by adding, removing, or reordering sections. The content for sections is still defined by the section's schema.


Two Ways to Add Content: Native Data and Theme Settings

Once you have the structure, your content comes from one of two places.


Native Shopify Data

Shopify provides data objects, depending on page context.

For example:

  • On a product page: product
  • On a collection page: collection
  • On the cart page: cart

Access them like this:

{{ product.title }}
{{ collection.products | size }}
{{ cart.total_price | money }}

You do not need to fetch or import this data. Shopify adds it automatically based on routing.


Theme Editor Settings (Schema)

Fields defined in the schema of your sections are editable in the Theme Editor UI.

{{ section.settings.title }}
{{ block.settings.label }}

You often combine these with Shopify’s native data for dynamic, editable themes.


Quick Folder Structure Recap

Here's how it fits together in files:

/templates/product.liquid
  └── references sections (e.g. main-product, related-products)
/sections/main-product.liquid
  └── defines blocks (e.g. features, highlights)
/snippets/
  └── reusable logic or markup
/assets/
  └── theme.css, app.js, fonts, images
/templates/index.json
  └── declares dynamic sections and their order

Bonus: What About Tailwind or React?

You can use Tailwind or React, but you must build them outside Shopify and upload as static assets.

Tailwind CSS

  1. Set up Tailwind locally.
  2. Let Tailwind scan .liquid files for class usage.
  3. Build your theme.css using Tailwind and PurgeCSS.
  4. Upload theme.css to /assets/ and link it in your theme layout.

React

  1. Build with Vite or another bundler.
  2. Output a bundled JS file (for example, app.js).
  3. Upload it to /assets/.
  4. Reference the script in your Liquid layout and mount to a DOM node.

I'll cover this workflow in a separate article soon.


Final Thoughts

If you are coming from React or Next.js, Shopify's Liquid system can feel strange at first. Once you understand:

  • Templates, sections, and blocks
  • Native data vs. Theme Editor settings
  • How schema ties everything together

it all becomes clearer.

This mental model helps you build dynamic, flexible, and merchant-friendly themes without overcomplication. As you get more advanced, it's easy to bring in Tailwind, React, or APIs.

Let me know if you want to see more articles about dynamic sections, metafields, performance, or deployment workflows.

9

Comments

Enjoyed this article?
Subscribe to my newsletter for more insights and tutorials.
Matija Žiberna
Matija Žiberna
Full-stack developer, co-founder

I'm Matija Žiberna, a self-taught full-stack developer and co-founder passionate about building products, writing clean code, and figuring out how to turn ideas into businesses. I write about web development with Next.js, lessons from entrepreneurship, and the journey of learning by doing. My goal is to provide value through code—whether it's through tools, content, or real-world software.

You might be interested in