成功有了基本檔案 現在試著開發我的推薦模組外掛
首先發現 在 theme editor 加入我的 app block 的話
沒辦法在右側選單 動態選取在 admin panel 建立好的 recommendation set
因為右側選單的內容 是寫死在 liquid 檔案內的 schema 裡面的
所以只能做成文字輸入欄位 然後叫用戶去複製 id 再貼過來
不盡理想 但 shopify 生態系好像就是這樣
我先試著讓 extension 可以在 storefront 商店前台顯示出來
// blocks/recommendations.liquid
<div id="akawa-recommendations-root" data-set-id="{{ block.settings.recommendation_set_id }}"></div>
<script src="{{ 'index.js' | asset_url }}"></script>
{% schema %}
{
"name": "Akawa Recommendations",
"target": "section",
"settings": [
{
"type": "text",
"id": "recommendation_set_id",
"label": "Recommendation Set ID",
"default": "Click 'Manage app' below to copy the ID from admin panel",
"info": "Set ID can be found in the app admin panel."
}
]
}
{% endschema %}
這邊定義了 theme editor 會看到的資訊以及可編輯內容
同時定義了 storefront 會載入的內容 我先做成 vanilla js 讀取
未來再改成 react js
// assets/index.js
document.addEventListener("DOMContentLoaded", async () => {
const container = document.getElementById("akawa-recommendations-root");
if (!container) return;
const setId = container.dataset.setId;
if (!setId) {
container.innerText = "No recommendation set ID found.";
return;
}
try {
const res = await fetch(`https://akawa-upsell.turn.tw/api/recommendations?set_id=${setId}`);
if (!res.ok) throw new Error("Failed to fetch recommendations");
const json = await res.json();
const items = json.data;
if (items.length === 0) {
container.innerText = "No recommendations found.";
return;
}
// 簡單渲染卡片
const list = document.createElement("div");
list.style.display = "flex";
list.style.flexWrap = "wrap";
list.style.gap = "16px";
items.forEach((item) => {
const card = document.createElement("div");
card.style.border = "1px solid #ddd";
card.style.padding = "12px";
card.style.borderRadius = "8px";
card.style.width = "150px";
card.innerHTML = `
<img src="${item.image}" alt="${item.title}" style="width: 100%; height: auto;" />
<p style="font-weight: bold; margin: 8px 0 4px;">${item.title}</p>
<p style="color: gray;">${item.override_price ? `$${item.override_price}` : ''}</p>
<a href="/products/${item.handle}">View</a>
`;
list.appendChild(card);
});
container.appendChild(list);
} catch (err) {
container.innerText = "Error loading recommendations.";
console.error(err);
}
});
然後查看 theme editor
非常成功!有看到 Error loading recommendations
文字,因為我還沒實作 api
再來試著建立後端 api 先放假資料就好
// routes/api.recommendations.js
import { json } from '@remix-run/node';
export async function loader({ request }) {
const url = new URL(request.url);
const setId = url.searchParams.get('set_id');
if (!setId) {
return json({ error: 'Missing set_id' }, { status: 400 });
}
// 從資料庫查詢對應推薦商品
const recommendations = await fetchRecommendationsFromDB({
recommendationSetId: setId,
});
return json({ data: recommendations }, {
headers: {
"Access-Control-Allow-Origin": "*", // 或指定你的 Shopify 網域
"Access-Control-Allow-Headers": "Content-Type",
}
});
}
async function fetchRecommendationsFromDB({ recommendationSetId }) {
return [
{
title: "Product A",
override_price: "100",
image: "https://cdn.shopify.com/s/files/1/0533/2089/files/placeholder-images-image_large.png",
},
{
title: "Product B",
override_price: "200",
image: "https://cdn.shopify.com/s/files/1/0533/2089/files/placeholder-images-image_large.png",
},
];
}
然後這個 api 要記得部署到主機上 因為前面 api endpoint 是寫死的 在 npm run dev 環境讀取不到
注意還要處理 cors 問題
出來的成果如圖,非常成功!這就是 admin panel + storefront + my own api server 三方互動的流程!
老話一句 老實說我覺得這流程開發起來 門檻很高
有些程式碼與資料 要存在 shopify,有些要部署到有網址的線上主機,有些又希望在 local 能被 hot reload 也就是用 npm run dev 就能跑,開發體驗才會好
要有相當的前後端經驗,才比較清楚這整套互動的背後邏輯