Liquid 布局模板开发:layout 越薄,主题越好维护
layout/theme.liquid 是每个页面的最外层包装。它最容易被写坏的方式不是语法错,而是装了太多东西:页头页脚直接硬编码在里面、第三方脚本一层层往 <head> 里加、甚至按设备分出 theme.mobile.liquid。结果是商家在编辑器里什么都改不了,任何调整都得找开发。
现在的分工很清晰:layout 只负责 HTML 骨架与全局资源,一切可编辑内容交给 sections 与 section groups。这一篇讲清这条边界怎么落地。
一、theme.liquid 的必需元素
一个合法的 layout 只有两个硬要求:
| 标签 | 位置 | 作用 |
|---|---|---|
{{ content_for_header }} | <head> 内 | Shopify 注入脚本、埋点、应用所需的头部内容 |
{{ content_for_layout }} | <body> 内 | 渲染当前模板的内容 |
这两个都不能省、不能挪到别处、不能包在条件里。content_for_header 缺失会让后台功能、应用和分析大面积失效——而且症状往往滞后出现,很难查。
一个干净的骨架:
<!doctype html>
<html lang="{{ request.locale.iso_code }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ page_title }}</title>
{% if page_description %}
<meta name="description" content="{{ page_description | escape }}">
{% endif %}
<link rel="canonical" href="{{ canonical_url }}">
{{ content_for_header }}
</head>
<body class="template-{{ template.name }}">
<a class="skip-link" href="#main">{{ 'general.skip_to_content' | t }}</a>
{% sections 'header-group' %}
<main id="main" role="main">
{{ content_for_layout }}
</main>
{% sections 'footer-group' %}
</body>
</html>几个细节值得说明:
lang 用 request.locale.iso_code,不要写死 zh-CN。多语言店铺里写死等于告诉搜索引擎所有语言版本都是同一种语言。
跳转链接(skip-link)是无障碍基本项,配合 <main id="main"> 让键盘用户能跳过页头。文案走 {{ 'key' | t }},别硬编码中文——同时必须去 locales/en.default.json 加上对应键,否则 theme-check 会直接报 does not have a matching entry,商家侧则渲染成一段可见的键名:
{
"general": {
"skip_to_content": "Skip to content"
}
}这条是新手最常漏的一步:| t 只负责查表,不负责建表。
template.name 挂在 body 上,给 CSS 一个稳定的页面类型钩子,比在 layout 里写一堆 {% if template == ... %} 分支干净得多。
二、用 section groups 取代布局里的静态 section
这是老教程和现代主题最大的差距。过去页头页脚是这样写的:
{% section 'announcement-bar' %}
{% section 'header' %}这种写法商家只能改该 section 内部的设置,不能调整顺序、不能增删。section groups 解决了这个问题:把它们收进一个 JSON 文件,layout 里只留一个标签。
文件:sections/header-group.json
{
"type": "header",
"name": "Header Group",
"sections": {
"announcement-bar": { "type": "announcement-bar", "settings": {} },
"header": { "type": "header", "settings": {} }
},
"order": ["announcement-bar", "header"]
}然后在 layout 里把两行静态 section 换成一行:
{% sections 'header-group' %}注意标签是 sections(复数),参数是 group 文件名(不含 .json)。
group 文件的四个字段:type(组类型,如 header / footer / aside)、name(编辑器里显示的名字)、sections(以 ID 为键的 section 数据)、order(渲染顺序的 ID 数组)。和 JSON 模板一样,一个 group 最多渲染 25 个 section,每个 section 最多 50 个 block。
迁移时的一个好消息:当商家从”用静态 section”的旧版本更新到”把该 section 收进 group”的新版本时,Shopify 会尝试把原静态 section 的设置自动复制到 group 里对应 section 上,映射依据是 section 的类型与 ID。所以迁移不一定会丢配置——但仍应在预览主题里验证一遍,尤其是你同时改了 schema 字段名的时候。
三、备用布局:什么时候真的需要第二个 layout
多个 layout 是合法的,但理由必须是页面骨架结构不同,而不是内容不同。
| 场景 | 该不该新建 layout |
|---|---|
| 密码页 | 应该。layout/password.liquid 是平台约定 |
| 落地页要去掉页头页脚 | 应该。骨架确实不同 |
| 结账后的感谢页需要极简结构 | 应该 |
| 手机端想换个样子 | 不应该。用 CSS 响应式,见下一节 |
| 某个页面文案不同 | 不应该。那是 section 设置的活 |
指定 layout 有两种方式:
Liquid 模板里用 {% layout %}:
{% layout 'full-width' %}JSON 模板里用根级 layout 属性。它接受布局文件名,也接受 false:
{
"layout": "full-width",
"sections": {
"main": { "type": "main-page" }
},
"order": ["main"]
}"layout": false 表示不套任何布局渲染。要注意它的代价:没有布局的模板无法在主题编辑器里定制。除了极特殊的场景(比如需要输出纯净结构的页面),一般不要用。
JSON 模板还有一个 wrapper 属性,用来指定包裹所有 section 的 HTML 元素——需要给整个模板套一层语义容器时用它,而不是在每个 section 里各写一层 div。
四、三个常见的坏做法
按设备拆布局。 老教程里常见 theme.mobile.liquid 加 UA 判断。这在 Shopify 上是错的:页面有 CDN 缓存,服务端按 UA 分流会导致缓存串味;而且你要维护两套骨架。响应式 CSS 加上按需渲染就够——真的需要区分时,用 CSS 媒体查询和容器查询,而不是两个 layout。
把第三方脚本堆进 <head>。 每加一个同步脚本,所有页面的首屏都变慢一点。能延后的用 defer,能不加的走应用的 app embed(商家可自行开关),需要提前建连的用 preconnect 而不是直接引脚本。
在 layout 里写业务内容。 判断标准很简单:这段内容商家会想改吗? 会 → 它属于某个 section。layout 里出现具体文案、具体商品、具体促销信息,都是设计错位。
五、性能上真正值得做的两件事
不要在 layout 里做花活,值得做的只有两类:
给关键资源提前建连或预载。 首屏字体和 LCP 图片可以用 preload;第三方域名用 preconnect。注意 preload 是稀缺资源,超过两三个就会互相抢带宽,反而变慢。
让非关键 JS 让路。 用 defer 而不是把脚本塞在 </body> 前就算完事;组件级的脚本用 {% javascript %} 写在自己的 section / block 里,由平台合并,而不是全塞进一个全局文件。
组件级 CSS/JS 只在 snippets/、blocks/、sections/ 里可用——layout 里不能用 {% stylesheet %} / {% javascript %},这是平台限制,不是风格偏好。
六、上线自检
{{ content_for_header }}在<head>内,{{ content_for_layout }}在<body>内,都没被条件包裹;<html lang>取自request.locale.iso_code;- 页头页脚走
{% sections '...-group' %},layout 里没有硬编码的{% section %}; - 有 skip-link 与
<main id="main">; - layout 里没有具体业务文案;
- 没有按设备拆分的第二套 layout;
<head>里没有可以defer却同步加载的脚本;- 所有用户可见文案走
{{ 'key' | t }}; - 新增 layout 后,用到它的模板确实声明了
{% layout %}或 JSON 的layout。
七、延伸阅读
- 自定义分区开发 — section、theme blocks 与 app blocks 的选型
- 主题设置配置 — 全局设置放什么、不放什么
- 自定义代码片段 — 用 snippets 抽取重复结构
- 性能优化 — 渲染与资源加载成本
- 响应式设计 — 用 CSS 而不是多套布局解决设备差异
- 提高在线商店性能 — 站点级性能视角
八、官方文档
小结:layout 的职责只有 HTML 骨架与全局资源两件事。必需元素是
<head>里的{{ content_for_header }}和<body>里的{{ content_for_layout }},缺了会造成滞后且难查的故障。页头页脚不要写静态{% section %},改用sections/*-group.json加{% sections %},商家才能增删排序(且从旧版本更新时平台会尝试自动迁移设置)。新建 layout 的唯一正当理由是骨架结构不同——按设备拆布局在有 CDN 缓存的平台上是错的,用响应式 CSS 解决。