Shopify 主题开发完整指南
Shopify 主题开发自 2021 年 Online Store 2.0 推出后发生了根本性变化——从基于固定模板的”主题”,演进为以 Sections + Blocks + JSON Templates 为核心的”可视化拖拽系统”。本文聚焦在生产级主题开发的关键模式与决策,不重复 Liquid 基础语法(基础语法见 Liquid 文档)。
阅读前置:
- 熟悉 Liquid 基础(assign、for、if、render)
- 熟悉 Shopify 主题目录结构
- 推荐先看 Liquid 进阶技巧
一、Online Store 2.0 与旧主题的差异
主题文件结构(2.0)
my-theme/
├── assets/ # 静态资源(CSS、JS、字体、图片)
├── config/
│ ├── settings_schema.json # 主题设置
│ └── settings_data.json # 主题配置实际值
├── layout/
│ └── theme.liquid # 主布局
├── sections/ # 可复用 section 与 block
│ ├── header.liquid
│ ├── footer.liquid
│ └── *.liquid
├── snippets/ # 可复用代码片段
├── templates/ # 页面模板(JSON 或 Liquid)
│ ├── product.json # 2.0 推荐:JSON 模板
│ ├── product.liquid # 旧:Liquid 模板
│ ├── collection.json
│ └── index.json
├── locales/ # 多语言文件
└── blocks/ # 2024 新增:theme blocks(更细粒度)JSON Templates 取代 Liquid Templates
旧主题:
{# templates/product.liquid #}
<div class="product">
{% section 'product-info' %}
{% section 'product-recommendations' %}
</div>2.0 主题:
{
"sections": {
"main": { "type": "main-product", "settings": {} },
"recommendations": { "type": "product-recommendations", "settings": {} }
},
"order": ["main", "recommendations"]
}JSON 模板让运营人员可以在主题编辑器中拖拽改变 section 顺序,无需开发介入。
Sections Everywhere
旧主题:sections 仅在首页可用,其他页面是静态 Liquid。
2.0:每个页面都可由多个 sections 组成,每个 section 可包含多个 blocks。运营自由组合。
推荐做法
- 所有新主题项目用 2.0 架构
- 老主题升级时,把 Liquid templates 改为 JSON
- 主 section(如 main-product)建为完整的 section,子组件用 blocks
二、Section 与 Block 设计
Section Schema 完整结构
{% schema %}
{
"name": "Featured Products",
"tag": "section",
"class": "section-featured-products",
"settings": [
{
"type": "text",
"id": "title",
"label": "标题",
"default": "热门产品"
},
{
"type": "collection",
"id": "collection",
"label": "集合"
},
{
"type": "range",
"id": "products_per_row",
"label": "每行产品数",
"min": 2,
"max": 6,
"step": 1,
"default": 4
}
],
"blocks": [
{
"type": "product_card",
"name": "产品卡片",
"settings": [
{
"type": "product",
"id": "product",
"label": "产品"
}
]
}
],
"presets": [
{
"name": "Featured Products",
"category": "Products"
}
],
"max_blocks": 12
}
{% endschema %}Block 设计原则
- 每种 block type 独立 schema 定义
- 多个 block 可在同一 section 中重复(运营可加多个产品 / 多张图)
- 单 section 的 blocks 总数有上限(默认 16,可在 schema 设上限)
- 不同 block type 的 settings 完全独立
渲染 blocks
<div class="featured-products">
{%- if section.settings.title != blank -%}
<h2>{{ section.settings.title }}</h2>
{%- endif -%}
<div class="product-grid product-grid--{{ section.settings.products_per_row }}-cols">
{%- for block in section.blocks -%}
{%- case block.type -%}
{%- when 'product_card' -%}
<div class="product-grid__item" {{ block.shopify_attributes }}>
{%- if block.settings.product != blank -%}
{% render 'product-card', product: block.settings.product %}
{%- endif -%}
</div>
{%- endcase -%}
{%- endfor -%}
</div>
</div>{{ block.shopify_attributes }} 必填——让主题编辑器能识别并高亮可点击的 block。
三、theme.liquid 布局规范
头部规范
<!doctype html>
<html lang="{{ request.locale.iso_code }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="{{ settings.color_brand }}">
{%- liquid
assign page_title_with_brand = page_title
unless page_title contains shop.name
assign page_title_with_brand = page_title | append: ' - ' | append: shop.name
endunless
-%}
<title>{{ page_title_with_brand }}</title>
<meta name="description" content="{{ page_description | default: shop.description | escape }}">
<link rel="canonical" href="{{ canonical_url }}">
{# Hreflang 多语言 #}
{%- for locale in shop.published_locales -%}
<link rel="alternate"
hreflang="{{ locale.iso_code }}"
href="{{ canonical_url }}">
{%- endfor -%}
<link rel="alternate" hreflang="x-default" href="{{ canonical_url }}">
{# 预连接 + DNS Prefetch #}
<link rel="preconnect" href="https://cdn.shopify.com" crossorigin>
<link rel="preconnect" href="https://fonts.shopifycdn.com" crossorigin>
{# 关键 CSS(首屏) #}
{{ 'critical.css' | asset_url | stylesheet_tag: preload: true }}
{# 非关键 CSS(异步) #}
<link rel="preload" href="{{ 'theme.css' | asset_url }}" as="style"
onload="this.onload=null;this.rel='stylesheet'">
{{ content_for_header }}
</head>
<body class="template-{{ template | replace: '.', '-' }}">
<a class="skip-link" href="#MainContent">跳转到主要内容</a>
{% section 'announcement-bar' %}
{% section 'header' %}
<main id="MainContent" role="main">
{{ content_for_layout }}
</main>
{% section 'footer' %}
{# 非关键 JS 延迟加载 #}
<script src="{{ 'theme.js' | asset_url }}" defer></script>
</body>
</html>关键点
lang属性自动取当前 locale<title>与shop.name智能拼接避免重复- canonical 与 hreflang 默认输出
- preconnect Shopify CDN 减少 DNS 时间
- critical.css 内联 + theme.css 异步加载
四、性能优化模式
性能问题的常见来源
按真实项目影响排序:
- 循环外缺少
assign缓存集合对象 image_url未指定 width 或 width 过大- 首屏图片错加
loading="lazy" <img>缺 width/height(CLS 飙升)- 第三方脚本同步加载(INP 飙升)
详细诊断见 Shopify 商店性能优化。
主题代码层面的性能模式
模式 1:响应式图片 snippet
{# snippets/responsive-image.liquid #}
{%- assign sizes = sizes | default: "100vw" -%}
{%- assign widths = widths | default: "300, 600, 900, 1200" -%}
{%- assign widths_array = widths | split: ", " -%}
{%- capture srcset -%}
{%- for w in widths_array -%}
{{ image | image_url: width: w }} {{ w }}w{%- unless forloop.last -%},{%- endunless -%}
{%- endfor -%}
{%- endcapture -%}
<img
src="{{ image | image_url: width: 600 }}"
srcset="{{ srcset }}"
sizes="{{ sizes }}"
alt="{{ image.alt | escape }}"
loading="{{ loading | default: 'lazy' }}"
fetchpriority="{{ priority | default: 'auto' }}"
width="{{ image.width }}"
height="{{ image.height }}"
>调用:
{# 首屏 hero 图 #}
{% render 'responsive-image',
image: section.settings.hero_image,
sizes: '100vw',
loading: 'eager',
priority: 'high' %}
{# 产品卡片图 #}
{% render 'responsive-image',
image: product.featured_image,
sizes: '(min-width: 768px) 33vw, 100vw',
widths: '300, 600' %}模式 2:循环外预加载
{%- liquid
assign featured_products = collections['featured'].products
assign product_count = featured_products.size
-%}
{%- if product_count > 0 -%}
{%- for product in featured_products limit: 8 -%}
{%- render 'product-card', product: product -%}
{%- endfor -%}
{%- endif -%}模式 3:第三方脚本延迟
<script>
function loadDeferred() {
if (window.deferredLoaded) return;
window.deferredLoaded = true;
var scripts = ['{{ "tracking.js" | asset_url }}', 'https://example.com/widget.js'];
scripts.forEach(function(src) {
var s = document.createElement('script');
s.src = src;
s.async = true;
document.head.appendChild(s);
});
}
['scroll', 'click', 'keydown'].forEach(function(e) {
window.addEventListener(e, loadDeferred, { once: true, passive: true });
});
setTimeout(loadDeferred, 5000);
</script>五、JavaScript 架构
现代 Shopify 主题 JS 趋势
- Web Components:原生 Custom Elements,零依赖
- Alpine.js:轻量框架,适合简单交互
- Stimulus:HTML-first 框架
- vanilla TypeScript:性能最优但开发复杂
不推荐:
- jQuery(过时、体积大)
- 完整 React 框架(除非 Headless)
- 多个框架混用
Web Components 示例:Cart Drawer
class CartDrawer extends HTMLElement {
constructor() {
super();
this.openButton = this.querySelector('[data-open]');
this.closeButton = this.querySelector('[data-close]');
}
connectedCallback() {
this.openButton?.addEventListener('click', () => this.open());
this.closeButton?.addEventListener('click', () => this.close());
document.addEventListener('cart:updated', () => this.refresh());
}
open() {
this.setAttribute('aria-hidden', 'false');
document.body.style.overflow = 'hidden';
}
close() {
this.setAttribute('aria-hidden', 'true');
document.body.style.overflow = '';
}
async refresh() {
const response = await fetch('/cart.js');
const cart = await response.json();
this.updateUI(cart);
}
updateUI(cart) {
this.querySelector('[data-cart-count]').textContent = cart.item_count;
this.querySelector('[data-cart-subtotal]').textContent =
this.formatMoney(cart.total_price);
}
formatMoney(cents) {
return (cents / 100).toFixed(2);
}
}
customElements.define('cart-drawer', CartDrawer);<cart-drawer aria-hidden="true">
<button data-open>购物车 ({{ cart.item_count }})</button>
<div class="drawer">
<button data-close>关闭</button>
<ul>
{%- for item in cart.items -%}
{# 渲染 item #}
{%- endfor -%}
</ul>
<p>小计:<span data-cart-subtotal>{{ cart.total_price | money }}</span></p>
</div>
</cart-drawer>Cart API 交互
// 加入购物车
async function addToCart(variantId, quantity) {
const response = await fetch('/cart/add.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id: variantId, quantity: quantity })
});
if (!response.ok) {
throw new Error('Add to cart failed');
}
const item = await response.json();
document.dispatchEvent(new CustomEvent('cart:added', { detail: item }));
document.dispatchEvent(new CustomEvent('cart:updated'));
return item;
}
// 更新数量
async function updateCartItem(line, quantity) {
const response = await fetch('/cart/change.js', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ line, quantity })
});
return response.json();
}六、SEO 集成
自动生成的结构化数据
针对产品页:
{# snippets/product-schema.liquid #}
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "Product",
"name": {{ product.title | json }},
"image": [
{%- for image in product.images limit: 5 -%}
{{ image | image_url: width: 1200 | json }}
{%- unless forloop.last -%},{%- endunless -%}
{%- endfor -%}
],
"description": {{ product.description | strip_html | json }},
"sku": {{ product.selected_or_first_available_variant.sku | json }},
"brand": {
"@type": "Brand",
"name": {{ product.vendor | json }}
},
"offers": {
"@type": "Offer",
"url": {{ shop.url | append: product.url | json }},
"priceCurrency": {{ cart.currency.iso_code | json }},
"price": "{{ product.price | divided_by: 100.00 }}",
"availability": "{%- if product.available -%}https://schema.org/InStock{%- else -%}https://schema.org/OutOfStock{%- endif -%}",
{% if product.metafields.reviews.rating_count > 0 %}
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "{{ product.metafields.reviews.rating.value }}",
"reviewCount": "{{ product.metafields.reviews.rating_count }}"
}
{% endif %}
}
}
</script>Meta 标签
{# Open Graph #}
<meta property="og:type" content="{% if template contains 'product' %}product{% else %}website{% endif %}">
<meta property="og:title" content="{{ page_title | escape }}">
<meta property="og:description" content="{{ page_description | default: shop.description | escape }}">
<meta property="og:url" content="{{ canonical_url }}">
{%- if template contains 'product' -%}
<meta property="og:image" content="https:{{ product.featured_image | image_url: width: 1200 }}">
<meta property="product:price:amount" content="{{ product.price | divided_by: 100.0 }}">
<meta property="product:price:currency" content="{{ cart.currency.iso_code }}">
{%- endif -%}
{# Twitter Card #}
<meta name="twitter:card" content="summary_large_image">七、可访问性(A11y)
语义化 HTML
{# 不推荐 #}
<div class="header">
<div class="nav">
<div class="menu-item"><a href="...">Home</a></div>
</div>
</div>
{# 推荐 #}
<header>
<nav aria-label="主导航">
<ul>
<li><a href="..." {% if request.path == '/' %}aria-current="page"{% endif %}>Home</a></li>
</ul>
</nav>
</header>表单标签
<form action="/contact" method="post">
<label for="contact-email">电子邮箱</label>
<input id="contact-email" name="contact[email]" type="email" required
aria-describedby="email-help">
<small id="email-help">仅用于回复您的咨询</small>
<button type="submit">提交</button>
</form>跳转链接
<a class="skip-link" href="#MainContent">跳转到主要内容</a>配合 CSS:
.skip-link {
position: absolute;
left: -9999px;
}
.skip-link:focus {
position: fixed;
top: 0;
left: 0;
background: white;
padding: 1rem;
z-index: 9999;
}颜色对比
按 WCAG AA 标准:
- 正文文字 ≥ 4.5:1
- 大字号 ≥ 3:1
- UI 组件 ≥ 3:1
用浏览器 DevTools Lighthouse Accessibility 检查。
八、Shopify CLI 开发流程
安装
npm install -g @shopify/cli @shopify/theme
shopify auth login本地开发
# 拉取主题代码
shopify theme pull --store=my-store.myshopify.com --theme=THEME_ID
# 启动本地预览
cd my-theme
shopify theme dev --store=my-store.myshopify.com
# 修改代码 → 自动热重载推送修改
# 推送到 unpublished theme(推荐先测试)
shopify theme push --unpublished
# 直接更新现有主题
shopify theme push --theme=THEME_ID检查代码
# 静态检查
shopify theme check
# 输出错误、警告、建议九、生产部署流程
推荐工作流
本地开发
↓ shopify theme push --unpublished
Unpublished Theme(测试)
↓ Theme editor 测试
↓ Lighthouse 性能验证
↓ 关键流程手动测试(购买、加购、登录)
Live Theme(生产)版本管理
主题代码用 Git 管理:
main # 当前生产版本
develop # 开发分支
feature/xxx # 功能开发每次发布 tag:v1.2.3,便于回滚。
灰度发布
大改动时:
- 推送到 unpublished theme
- 通过 Shopify Theme Preview 共享 preview URL
- 内部测试 1-3 天
- 灰度部分流量(用第三方 A/B 工具如 Intelligems)
- 全量切换
十、避免的常见错误
错误 1:未升级到 Online Store 2.0
旧 Liquid templates 已不再获得 Shopify 新功能支持。新主题项目必须 2.0。
错误 2:硬编码英文文案
{# 反例 #}
<button>Add to Cart</button>
{# 推荐 #}
<button>{{ 'products.product.add_to_cart' | t }}</button>错误 3:忽略 block.shopify_attributes
未输出该属性的 block 在主题编辑器中不能被选中编辑。
错误 4:在主题中存储大量逻辑
复杂业务逻辑应该在应用 / 服务器端实现,主题只负责展示。
错误 5:不验证主题在 mobile 上的表现
主题改完只在桌面测试。多数 Shopify 流量来自移动端。
十一、相关教程
最后更新时间: