招聘方从这里看 →

同一家店,第二个店面

打开 Liquid 主题版

PORTVA 在同一家 Shopify 店铺上还有一套手写的 Liquid 主题。Shopify 开发店一定会先显示密码页——粘贴一次密码,浏览器就会记住。

店铺密码paucai
PORTVA

购物车

购物车是空的。

继续购物

方案案例

七个问题,以及为什么这样解决

前三个来自一家线上 DTC 品牌店,已匿名;其余的做在 PORTVA 上。每个案例都讲清难在哪、有哪些可选方案、选中的方案要付出什么代价。

01 · 线上店铺 · 全栈

只有数据确认了,才告诉顾客“适配”

所有页面改读同一份三状态适配结果,数据缺失不再被显示成承诺。

这家店卖的商品只适配特定型号,每个页面都要回答一个问题:这个商品我能用吗?以前字段留空就等于“全部适配”,而且不同页面各算各的。

看完整过程:难点、方案取舍、验证和代价

难点

  • 适配数据有不少缺失,原有结构里没有“未知”这个值。
  • 型号页、商品页、尺寸指南和套装页都需要这个结果。
  • 同一型号可能有多种配置。
  • 没选型号的顾客仍然要能正常购买。

可选方案

  • 保留“适配 / 不适配”未采用

    简单,但数据缺失仍会变成承诺,最后变成退货和投诉。

  • 逐个页面打补丁未采用

    单页最快,但同一型号和同一商品在不同页面可能得到不同答案。

  • 统一结果:适配、有条件适配、未知采用

    页面只显示结果,不自行猜测。未核实的绝不显示成“适配”,有条件的就再问顾客一个问题。

最终实现

  • 型号页、商品页、指南页和套装页共用一份适配结果。
  • 有条件适配时,再追问一两个最关键的问题。
  • 没选型号时不阻挡单品购买,改为提供适配检查入口。

如何验证

  • 每次发布都经过多轮 PR 审查,并在有代表性的型号上做预览检查。
  • 有歧义的数据交给运营确认,不在代码里硬判。

代价

  • 状态没有审核完的型号,要等数据齐了才能用新逻辑。
  • 多了三种状态,每条购买路径都要处理“结果还没出来”的情况。

02 · 线上店铺 · 全栈

加一个型号,不用改主题

新增型号变成一次数据发布,运营用内部 Shopify App 就能发布和回退。

这家店按型号逐步上线专题页。如果手工做,每个型号都要改主题、找开发,一份错误数据还会直接到顾客面前。

看完整过程:难点、方案取舍、验证和代价

难点

  • 主题和整家线上店共用,每次改模板都有发布风险。
  • 运营用表格维护数据,上线前需要看到具体改了什么。
  • 数据量太大,无法在 Shopify 后台安全地手工编辑。

可选方案

  • 每个型号一个模板未采用

    前几个很快,但主题随型号不断膨胀,每个都要发版。

  • 直接在后台编辑未采用

    不用开发,但没有差异对比、没有审核,出错也回不去。

  • 数据驱动页面 + 版本化发布 App采用

    Metaobject 给每个型号标准网址;数据经过上传、校验、差异审核,再发布成新版本。

最终实现

  • 型号页由数据渲染,内链可被抓取,结构化面包屑与页面一致。
  • 基于 React Router、Node、Prisma 的 Shopify App:上传、校验、审核差异、发布、回退。
  • 大批量导入在后台 Worker 处理,前台读取当前生效的版本。

如何验证

  • 这个 App 已用于生产,维护这家店的兼容数据库。
  • 回退是常规操作:让前台指回上一个已发布的版本。

代价

  • App 是又一套需要托管和维护的系统。
  • 主题和 App 共用数据约定,改结构时两边要一起改。

03 · 线上店铺 · 埋点

让每次活动上线后都能算清效果

一次活动的效果无法评估之后,新页面和促销功能都带着设计好的 GA4 漏斗和订单归因上线。

一个促销功能上线时没有埋点,用的还是共享折扣码。活动结束后,没法比较用过和没用过的顾客,也无法可靠地把订单归因到它身上。

看完整过程:难点、方案取舍、验证和代价

难点

  • 团队的预览链接和上线当天的检查都被算成了真实访问。
  • 爬虫可能占据某一天的大部分流量,让所有比率失真。
  • GA4 有些参数名是电商数据保留的。
  • 事件里不能带个人信息。

可选方案

  • 只看 GA4 和 Shopify 默认报表未采用

    不用开发,但分不出某个功能或某个页面的漏斗。

  • 用折扣码归因未采用

    好读,但共享码会传播到功能之外,结果要么偏高要么说不清。

  • 开发前先设计漏斗采用

    提前定义曝光、点击、参与、加购、购买,并在购物车上带活动标记用于归因。

最终实现

  • 型号页和全站型号选择器的 GA4 漏斗事件。
  • 通过购物车属性做订单归因,并把适配型号传入弃购邮件。
  • 测试标记,让团队访问不进入报表。

如何验证

  • 对照真实报表检查事件,报表从上线次日起算,跳过测试流量。
  • 读取任何比率前,先排除已知的爬虫流量。

代价

  • 每个新功能都要多花埋点设计和验证的时间。
  • 要积累足够多的干净数据,报表才有参考价值。

04 · PORTVA · 全栈

不能卖错国家的规格

插头不匹配的订单会在结账时被拦下,不管它来自哪个店面或哪条购物车链接。

Portva 140 有美规和欧规两种插头。美规插头在德国用不了,所以这个规格在德国不能卖;但两种插头属于同一个商品、同一个页面、同一个规格选择器。

看德国市场下被禁用的美规插头 →

看完整过程:难点、方案取舍、验证和代价

难点

  • Shopify Markets 只能按商品控制可售范围,不能按规格。
  • 有 Next.js 和 Liquid 两个店面,而且不经过任何界面也能创建购物车。
  • 德语翻译会改掉选项名(Plug → Stecker),不能用选项名做判断依据。
  • Function 里拿到的是规格标题(如“Graphite / US”),不是选中的选项。

可选方案

  • 每种插头建一个商品,按市场发布未采用

    可售范围是原生的,但插头不再是选项:库存、网址和数据分析都按市场拆开,每加一个颜色就要多建商品。

  • 只在界面上隐藏错误插头未采用

    做得快,对多数顾客也够用,但分享的购物车链接或另一个店面可以直接绕过。

  • 店面规则 + 服务端校验采用

    两个店面都把该插头标为本市场不售,也不会默认选中;Cart Validation Function 在 Shopify 内部执行同一条规则。

最终实现

  • 每个选项值有三种状态:可售、售罄、本市场不售。
  • 选购助手和快速加购都会选当前市场能卖的规格。
  • 结账时以收货地址为准,不看浏览时的市场,错误提示有英文和德文。

如何验证

  • 4 组测试数据跑在编译后的 Function 上:欧规发德国通过,美规发德国被拦,美国市场的购物车发往德国被拦,没有规则的国家不受影响。
  • 过程中发现一个真实 bug:德语翻译改了选项名后规则失效,现在改为匹配插头代码。

代价

  • 国家和插头的对应表存在三处:Next.js、Liquid 和 Function。加第三个市场要改三个地方。
  • Function 从规格标题里读插头,所以改选项值时要同步修改。

05 · PORTVA · 前端

保留实时 3D,但不拖累首屏

PageSpeed Insights 以前测不完手机版首页,现在 95 分,3D 还在。

首页用实时 WebGL 模型讲产品故事。在中端手机上,它会占住主线程好几秒,而这时顾客还没看到任何能买的东西。

查看完整性能报告 →

看完整过程:难点、方案取舍、验证和代价

难点

  • three.js 和渲染器让首次加载多了约 260 KB 的 JavaScript。
  • 每帧都绘制的循环,在没有 GPU 的机器上会让页面一直无法空闲,而实验室测试正是这种环境。
  • 首屏仍要展示产品,并且和之后加载的实时模型对得上。

可选方案

  • 用视频代替 3D未采用

    轻量可靠,但失去了交互,而交互正是这一段的重点。

  • 保留 3D,等页面空闲后加载未采用

    首屏会好一些,但没碰过页面就离开的访客,手机也要下载模型,渲染循环也还在。

  • 先显示封面,首次交互才加载 three.js,只在滚动时绘制采用

    封面就是同一模型的渲染图,切换时是淡入淡出。画布只在滚动时间线变化时才绘制一帧。

最终实现

  • three.js 和模型在第一次滚动、触摸或移动鼠标时才加载。
  • 开启减少动态或不支持 WebGL 的访客看到内容相同的静态首屏。
  • 图片分阶段加载并预设尺寸,商品页的购买区移进了预渲染的 HTML。

如何验证

  • 手机版 PageSpeed(首页):优化前无法完成测试,优化后 95 分(三次测试中 LCP 居中的一次)。
  • 本地 Lighthouse,三次取中位数:JavaScript 557 → 295 KB,主线程耗时 4,435 → 1,596 ms,阻塞时间 522 → 55 ms。

代价

  • 在手机上,第一次触摸后约一秒,实时模型才替换封面。
  • 以后如果要加静止时的动画,需要自己请求绘制。
  • 商品页和列表页的 PageSpeed 变化在误差范围内。以上都是实验室数据,没有真实流量可以印证。

06 · PORTVA · 全栈

每个市场一个真实价格,从商品页一直到结账

德国顾客在两个店面和结账页看到的,都是同一个含增值税的固定欧元价。

德国价格原本由美元每日换算,会出现 €108.95 这种自己变动的数字。德国顾客还必须看到含增值税的价格。

在线上看德国价格 →

看完整过程:难点、方案取舍、验证和代价

难点

  • 价格在四个地方分别读取:Headless 站点、Liquid 主题、购物车和结账扩展。
  • 商品数据要能缓存,所以每个网址必须对应唯一的市场。
  • 购物车绑定买家国家,其中有些商品行在另一个市场不能卖。

可选方案

  • 继续用自动换算未采用

    不用维护,但价格每天变,也不是品牌会定的数字。

  • 只用一个购物车,切换时改国家未采用

    价格会更新,但美规插头也会被带进德国的购物车。

  • 市场写进网址,独立价目表,每个市场一个购物车采用

    每个请求在到达 Shopify 前就知道市场,德国价格是人为定的,购物车也不会混用市场。

最终实现

  • 德国有独立的目录和固定欧元价:Portva 140 €129,Portva 65 €55,Link €21 和 €27。
  • 所有店面查询都按当前页面的国家和语言发起;购物车按该国家创建,每个市场存一个。
  • 两个店面都按市场显示“inkl. MwSt., zzgl. Versand”,德国结账页显示增值税。

如何验证

  • 德国测试结账暴露了一个真实 bug:推荐插件查询时没带市场,线材按美元报价。现在按买家所在国家查询,显示 21,00 €。
  • 配置脚本是幂等的,重复运行不会改变目录和价格。

代价

  • 切换市场会换一个购物车,已加的商品不会带过去。
  • 成本或汇率变动时,固定价格需要有人复核。
  • 购物车的免邮进度条在前端重复写了门槛 70,改规则要两边一起改。

07 · PORTVA · 全栈

到货提醒:同一个库存事件只处理一次

补货后 Shopify 发出 Webhook,线上 6 秒内更新了订阅状态,伪造请求和重复投递都不会造成影响。

售罄规格需要收集到货提醒。库存变化由 Shopify Webhook 通知,但 Webhook 可能重试、并发到达,公开接口也可能被伪造。

看售罄规格的提醒表单 →

看完整过程:难点、方案取舍、验证和代价

难点

  • 站点部署在 Vercel,没有自己的数据库。
  • Shopify 要求接口尽快响应,处理失败又必须能重试。
  • 这是概念店,不应该为了演示保存访客的真实邮箱。

可选方案

  • 只在内存里记录处理过的事件未采用

    Serverless 实例之间不共享内存,并发和重试都拦不住。

  • 为这个功能单独接数据库未采用

    能做到唯一约束,但多一套账号和运维,只为存两类小数据。

  • 用 Metaobject 存订阅,用事件 ID 作唯一 handle采用

    Shopify 保证 handle 唯一,两个并发投递只有一个能创建成功,不需要额外基础设施。

最终实现

  • 售罄规格显示提醒表单,同一规格和邮箱重复提交只保留一条记录,只保存打码邮箱。
  • 接口先对原始请求体校验 HMAC,再抢占事件,然后更新等待中的订阅。
  • 处理失败时释放抢占并返回 500,Shopify 重试时只处理仍在等待的订阅。

如何验证

  • 错误签名和篡改内容都返回 401。
  • 同一事件并发投递两次再重放一次,只产生一条事件记录。
  • 把线上售罄规格的库存临时加 1,真实 Webhook 6 秒内把订阅更新为已通知,库存随后调回 0。

代价

  • 不发真实邮件,要发需要接邮件服务,并改为保存完整邮箱和退订流程。
  • 每个事件最多处理 50 个订阅。
  • 事件记录没有清理任务,会一直累积。

阅读说明

线上店铺的案例隐去了品牌、内部数据和具体数字。PORTVA 是概念商店,结账为测试模式,没有真实流量,所以它的结果来自测试和实验室测量。源码不公开,面试时可以逐项讲解。