Remix 与 Shopify:绕过 Shopify 的 API 并走向开源
Shopify 似乎已经意识到开源正是开发者所需要的。他们先是推出了 Hydrogen,现在又更进一步,与 Remix 强强联手。这固然是好消息,但有些人可能仍然希望完全依赖开源和可定制性,构建一个涵盖所有技术栈的完全开源的系统。
本文将教你如何绕过 Shopify,仅使用开源工具,通过 Remix 搭建一个功能齐全的电子商务平台。三赢!
工作原理
首先,我们将结合两个最流行的开源项目的功能来取代 Shopify 的功能:
- Medusa是领先的开源电子商务平台,面向开发者,提供高度可定制性,并使用 Javascript 构建。
- Strapi是领先的开源内容管理系统,专为开发者打造,同样具有高度可定制性,并且是用 Javascript 构建的。
在本教程中,您将学习如何使用 Remix 和 Medusa & Strapi 构建电子商务店面,从而避免使用完整的 Shopify 后端。
您可以在此代码库中找到本文的源代码。以下是最终结果的预览:
先决条件
- Node v14 或更高版本
- 推荐使用Yarn,但您也可以使用 npm。
- Redis
- Medusa CLI:要安装 CLI,请运行
yarn global add @medusajs/medusa-cli.
设置 Strapi
安装模板
npx create-strapi-app strapi-medusa --template shahednasser/strapi-medusa-template
这会strapi-medusa在您的项目中创建一个名为 `<path_to_strapi_dashboard_name>` 的文件夹。安装完成后,Strapi 开发服务器将在 `<port_port_name>` 端口启动localhost:1337。同时,您的默认浏览器会打开一个新页面,供您创建新的管理员用户并登录。登录后,您即可访问 Strapi 控制面板。
更改用户的授权设置
您的 Medusa 服务器需要 Strapi 用户的凭据才能向 Strapi 导入模拟数据。要创建新用户,请转到内容管理器,然后在“集合类型”下选择“用户”。
点击右上角的“创建新条目”按钮。这将打开一个新表单,用于输入用户详细信息。
请输入用户的用户名、邮箱地址和密码。完成后,点击右上角的“保存”按钮。
接下来,转到“设置”→“角色”→“已验证”,选择所有权限,然后点击“保存”。
设置美杜莎
要启动 Medusa 服务器,请运行以下命令:
medusa new medusa-server --seed
该--seed标志会创建一个 SQLite 数据库,并用一些演示数据填充它。
切换到该medusa-server目录并转到medusa.config.js. 修改导出的对象末尾以启用 Redis:
module.exports = {
projectConfig: {
redis_url: REDIS_URL,
//...
}
//...
};
默认的 Redis 连接字符串是,redis://localhost:6379但如果您对其进行了更改,请转到该.env文件并添加以下内容:
REDIS_URL=<YOUR_REDIS_URL>
<YOUR_REDIS_URL>你的连接字符串在哪里?
此外,由于 Remix 店面运行在 上localhost:3000,因此您必须添加一个环境变量STORE_CORS来设置店面的 URL。
添加以下内容.env:
STORE_CORS=http://localhost:3000
安装 Strapi 插件
要安装 Strapi 插件,请在 Medusa 服务器目录中运行以下命令:
yarn add medusa-plugin-strapi
然后,添加以下环境变量:
STRAPI_USER=<STRAPI_IDENTIFIER>
STRAPI_PASSWORD=<STRAPI_PASSWORD>
STRAPI_PROTOCOL=http
STRAPI_URL=<STRAPI_URL> # Optional
STRAPI_PORT=<STRAPI_PORT> # Optional
在哪里:
<STRAPI_IDENTIFIER>是您在上一步中创建的用户的电子邮件地址或用户名。<STRAPI_PASSWORD>是您在上一步中创建的用户的密码。<STRAPI_PROTOCOL>是您的 Strapi 服务器的协议。由于您使用的是本地 Strapi 服务器,请将其设置为http。默认值为https。<STRAPI_URL>是您的 Strapi 服务器的 URL。默认情况下,URL 为localhost。<STRAPI_PORT>是 Strapi 服务器运行的端口。默认情况下,端口为1337。
最后,打开数组medusa-config.js并向其中添加以下新项plugins:
const plugins = [
//...
{
resolve: `medusa-plugin-strapi`,
options: {
strapi_medusa_user: process.env.STRAPI_USER,
strapi_medusa_password: process.env.STRAPI_PASSWORD,
strapi_url: process.env.STRAPI_URL, //optional
strapi_port: process.env.STRAPI_PORT, //optional
strapi_protocol: process.env.STRAPI_PROTOCOL //optional
}
}
];
测试集成
请确保 Strapi 服务器仍在运行。如果未运行,您可以在 Strapi 项目目录中运行以下命令来启动 Strapi 服务器:
yarn develop
请确保您的 Redis 服务器也已启动并运行。
然后,在 Medusa 服务器的目录中,运行以下命令启动 Medusa 服务器:
yarn start
这将启动您的 Medusa 服务器localhost:9000。您会看到一些product.created事件以及类似事件已被触发。
这将使用您提供的演示产品更新 Strapi。
在 Strapi 中添加 CMS 页面
现在您将使用 Strapi 来管理您店铺首页的内容。完成此操作后,您将能够通过 Strapi 控制三项内容:店铺顶部显示的标题文字;标题文字下方的副标题;以及首页上显示的产品列表。
在 Strapi 控制面板中,转到“插件”下的“内容类型构建器”。您可以在这里定义内容的模型/模式。
在“单一类型”下,点击“创建新的单一类型”。
输入显示名称“首页”(如果您使用了其他名称,稍后将需要使用相应的 API ID),然后点击继续。
接下来,选择组件字段,并将其显示名称设置为“Hero Text”,同时指定一个类别首页(点击类别下的“创建首页”)。然后,点击配置组件。
hero_text然后在下一步中给它命名,然后单击“完成”。
转到“组件”中的“首页”下的“英雄文本”组件,并创建三个名为“文本字段” start_text、“文本字段名称mid_text”和“文本字段名称”的文本字段end_text。
这里添加了三个文本字段,因为稍后在文章中,您将给它们添加特殊的下划线以mid_text突出显示。
返回“单一类型”下的“首页”类型,并为“产品”添加一个关联字段。关联关系应为“首页包含多个产品”。为其指定一个字段名称products_list。
最后,添加一个文本字段heading_2。保存首页内容类型的更改。
这是您的首页内容类型应该呈现的样子:
接下来,转到“设置”→“用户和权限插件”→“角色”→“公开”,并为首页和产品类型启用“查找”权限。点击保存。
现在,前往内容管理器,在“首页”下方添加您的主标题文字,并在右侧的“关联”部分添加您希望显示的产品。点击保存,然后发布。
设置 Remix 商店页面
在本节中,您将使用 Remix 设置电子商务商店。
Remix 提供了三个官方预置模板供您根据需求使用,但您也可以从基本模板开始,或者创建自己的模板。
设置混音
要设置 Remix 应用(请在与medusa-server和不同的目录中执行此操作strapi-medusa),请运行以下命令:
npx create-remix@latest my-storefront
它会问你几个问题。选择Just the basics,然后选择你偏好的托管平台(如果你不确定,可以选择 Remix App Server),选择 typescript,npm install如果你想使用,则选择 no yarn。
然后,切换到该my-storefront目录并使用 yarn 安装依赖项:
cd my-storefront
yarn install
配置 Tailwind CSS
安装 Tailwind CSS 来设计 UI 元素:
yarn add -D tailwindcss postcss autoprefixer concurrently
运行程序npx tailwindcss init创建tailwind.config.js文件。然后,将其内容设置为以下内容:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
另外,请修改 package.json 文件中的 scripts:
{
"scripts": {
"build": "npm run build:css && remix build",
"build:css": "tailwindcss -m -i ./styles/app.css -o app/styles/app.css",
"dev": "concurrently \"npm run dev:css\" \"remix dev\"",
"dev:css": "tailwindcss -w -i ./styles/app.css -o app/styles/app.css"
}
}
styles/app.css然后,创建包含以下内容的文件:
@tailwind base;
@tailwind components;
@tailwind utilities;
最后,将以下内容添加到app/root.tsx导入列表之后:
import styles from "./styles/app.css"
export function links() {
return [{ rel: "stylesheet", href: styles }]
}
现在您可以在应用中使用 Tailwind CSS 了。
将 Storefront 连接到 Medusa 服务器
完成这些步骤后,我们就可以将您的店铺连接到您的 Medusa 服务器了。
首先,您需要使用以下命令安装一些软件包:
yarn add medusa-react react-query @medusajs/medusa
该medusa-react库使用react-query作为服务器端状态管理解决方案,并将该库列为对等依赖项。
要使用 `<Medusa>` 提供的钩子medusa-react,您需要MedusaProvider在组件树的某个位置引入 `<Medusa>`。`<Medusa>`MedusaProvider接受一个baseUrl指向您的 Medusa 服务器的 prop。其底层medusa-react使用medusa-js客户端库(基于 `<Medusa>` 构建axios)与您的服务器交互。
此外,由于 medusa-react 是基于 react-query 构建的,因此您可以传递一个表示 react-query 的QueryClientProvider属性的对象,该对象MedusaProvider将被传递。
您还需要将您的应用程序包装在一个 <app> 中,CartProvider因为这样您就可以使用 Medusa 提供的购物车功能,您稍后会进行此操作。
创建一个文件app/lib/config.ts。该文件将包含您的medusaClient配置信息,以便您可以在应用程序中使用 Medusa 的 Javascript 客户端。
import Medusa from '@medusajs/medusa-js';
import { QueryClient } from 'react-query';
const MEDUSA_BACKEND_URL = 'http://localhost:9000';
const STRAPI_API_URL = 'http://127.0.0.1:1337/api';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
staleTime: 1000 * 60 * 60 * 24,
retry: 1,
},
},
});
const medusaClient = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, maxRetries: 3 });
export { MEDUSA_BACKEND_URL, STRAPI_API_URL, queryClient, medusaClient };
现在转到您的目录app/root.tsx并导入所需的软件包:
import { MedusaProvider, CartProvider } from 'medusa-react';
import { MEDUSA_BACKEND_URL, queryClient } from './lib/config';
您也可以在此处编辑meta以更改您的元数据
export const meta: MetaFunction = () => ({
charset: 'utf-8',
title: 'New Remix App',
viewport: 'width=device-width,initial-scale=1',
});
下面您将看到该App组件。在返回的 JSX 中,添加MedusaProvider一些CartProvider基本样式body:
return (
<html lang="en">
<head>
<Meta />
<Links />
</head>
<body className="bg-black text-slate-400 overflow-x-hidden justify-center flex">
<MedusaProvider
queryClientProviderProps={{ client: queryClient }}
baseUrl={MEDUSA_BACKEND_URL}
>
<CartProvider>
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</CartProvider>
</MedusaProvider>
</body>
</html>
);
Strapi 的主页显示
您的首页数据可在 Strapi 端点获取:(localhost:1337/api/home-page添加?populate=*参数可同时显示嵌套产品)。它返回一个包含 `<include>` 和data`<include>`meta数组的对象。您无需关心 `<include>` meta,真正需要关注的是 `<include>` data,它包含了您在 Strapi 控制面板中输入的所有内容。
app/types/StrapiResponse.ts首先,创建包含以下内容的文件:
// StrapiResponse.ts
export type StrapiResponseType = {
data: {
id: number;
attributes: {
createdAt: Date;
updatedAt: Date;
publishedAt: Date;
hero_text: {
id: number;
start_text: string;
mid_text: string;
end_text: string;
};
products_list: {
data: Array<StrapiProductData>;
};
heading_2: string;
};
};
meta: {};
};
export type StrapiProductData = {
id: number;
attributes: {
medusa_id: string;
title: string;
subtitle: string | null;
description: string;
handle: string;
is_giftcard: boolean;
status: 'draft' | 'proposed' | 'published' | 'rejected';
thumbnail: string;
discountable: boolean;
weight: number;
product_length: null;
width: null;
height: null;
hs_code: null;
origin_country: null;
mid_code: null;
material: string | null;
createdAt: Date;
updatedAt: Date;
};
};
这是 Strapi API 返回数据的格式。
接下来,创建一个实用函数,用于从 Strapi API 获取内容。创建一个app/models/home.server.ts包含以下内容的文件:
// home.server.ts
import { STRAPI_API_URL } from "~/lib/config";
import type {
StrapiProductData,
StrapiResponseType,
} from "~/types/StrapiResponse";
export const getHomePageData = async () => {
const homePage: StrapiResponseType = await (
await fetch(`${STRAPI_API_URL}/home-page?populate=*`)
).json();
const { data } = homePage;
const { attributes } = data;
const heroText = attributes.hero_text;
const products = attributes.products_list.data;
const smallHeading = attributes.heading_2;
const homePageData = { heroText, products, smallHeading };
return homePageData;
};
export type homePageDataType = {
heroText: {
id: number;
start_text: string;
mid_text: string;
end_text: string;
};
products: StrapiProductData[];
smallHeading: string;
};
在这个getHomePageData函数中,你应该只返回首页需要的数据。
在上面的代码示例中,你会注意到使用了 import 语句,这是因为它是 Remix 中默认设置的目录~的别名,如果你愿意,可以随时更改它。apptsconfig
目录下的所有文件app/routes都将构成一条路由。例如,目录app/routes/store.tsx将包含/store路由。
接下来,app/routes/index.tsx创建一个加载器函数:
import { getHomePageData, homePageDataType } from '~/models/home.server';
export const loader = async () => {
const homePageData = await getHomePageData();
return homePageData;
};
要使用从加载器函数收到的响应,您需要在组件useLoaderData内部使用 Remix 的 hook Index:
import { useLoaderData } from '@remix-run/react';
export default function Index() {
const { heroText, products, smallHeading } =
useLoaderData<homePageDataType>();
...
}
这里,homePageData它被解构并引入useLoaderData,现在你可以在你的页面上使用它了。
然后,将返回的 JSX 更改为以下内容:
export default function Index() {
//...
return (
<div className="px-10 sm:px-20 md:px-44 pt-44 max-w-[100rem] flex-grow w-screen">
{/* Hero Section */}
<div>
<h1 className="text-[2.5rem] sm:text-5xl lg:text-6xl xl:text-8xl relative font-medium lg:leading-[1.15] xl:leading-[1.2]">
{heroText.start_text}{' '}
{heroText.mid_text.split(' ').map((text) => (
<span key={text} className="text-gray-50">
<span className="relative">
{text}
<div className="h-1 bg-emerald-200 w-full absolute bottom-0 left-0 inline-block" />
</span>{' '}
</span>
))}
{heroText.end_text}
</h1>
</div>
</div>
);
}
heroText.start_textstart_text它会从您在 Strapi 中创建的组件下的文本字段中提取数据hero_text。同样,` from`heroText.mid_text和`from` 分别heroText.end_text是 Strapi 中的 `from`mid_text和 ` end_textfrom` 字段。
然后,mid_text为了使每个单词都有统一的下划线(如果有多个单词),它被拆分了,稍后您将在主页 UI 中看到这种情况。
app/components/productCard.tsx要展示您的产品,请创建包含以下内容的文件:
import { Link } from '@remix-run/react';
interface ProductCardType {
image: string;
title: string;
handle: string;
}
export default function ProductCard({ image, title, handle }: ProductCardType) {
return (
<Link to={`/products/${handle}`}>
<div className="flex flex-col space-y-1 p-2 hover:bg-slate-400 hover:bg-opacity-25 cursor-pointer active:scale-95 transition ease-in-out duration-75">
<img src={image} alt="" />
<h3 className="pt-2 text-white text-xl">{title}</h3>
</div>
</Link>
);
}
此元素Link来自 Remix,可帮助您跳转到产品页面。Medusa 产品中提供的 handle 属性将用作 slug。
现在回到你的页面app/routes/index.tsx,你需要将 Strapi 的响应(产品)映射到该页面。
在你的英雄部分下方执行此操作:
import ProductCard from '~/components/productCard';
export default function Index() {
...
return (
<div className="px-10 sm:px-20 md:px-44 pt-44 max-w-[100rem] flex-grow w-screen">
...
<div className="flex flex-col items-center pt-40 pb-44">
<h2 className="text-2xl sm:text-3xl lg:text-4xl pb-10 text-white">
{smallHeading}
</h2>
<div className="grid grid-cols-2 xl:grid-cols-4 gap-x-6">
{products.map(({ attributes }) => (
<ProductCard
key={attributes.medusa_id}
image={attributes.thumbnail}
handle={attributes.handle}
title={attributes.title}
/>
))}
</div>
</div>
</div>
)
测试主页
要测试您的主页,请启动您的 Remix 开发服务器yarn dev(确保您的 Medusa 和 Strapi 服务器已经运行)。
您的应用已准备就绪localhost:3000,界面如下所示:
使用 Medusa 实现添加到购物车功能
要将产品添加到购物车,首先需要将购物车与客户关联起来。为此,您可以为您的应用程序创建一个包装器,该包装器会检查购物车是否已初始化或需要创建,并执行必要的操作。
app/components/outletContainer.tsx创建包含以下内容的文件:
import { useCart } from 'medusa-react';
import { ReactNode, useEffect } from 'react';
import { medusaClient } from '~/lib/config';
interface OutletContainerType {
children: ReactNode;
}
export default function OutletContainer({ children }: OutletContainerType) {
const { setCart } = useCart();
useEffect(() => {
const localCartId = localStorage.getItem('cart_id');
localCartId
? medusaClient.carts.retrieve(localCartId).then(({ cart }) => {
setCart(cart);
})
: medusaClient.carts.create().then(({ cart }) => {
localStorage.setItem('cart_id', cart.id);
setCart(cart);
});
}, []);
return <div>{children}</div>;
}
你正在使用medusa-react`s`useCart钩子,setCart它会将你的购物车设置到全局位置。之后你可以在应用的任何位置使用它。它还outletContainer会将购物车保存到本地存储中,以便即使用户下次访问,添加的商品也会保留。
您还需要在商品添加到购物车时显示弹出式通知。请安装以下插件react-hot-toast来实现此功能:
yarn add react-hot-toast
现在,回到你的代码app/root.tsx,用 `<script>` 标签包裹你的<Outlet />代码OutletContainer。此外,添加`<script>`<Toaster />标签react-hot-toast,这将允许你显示通知:
import OutletContainer from './components/outletContainer';
import { Toaster } from 'react-hot-toast';
export default function App() {
return (
...
<CartProvider>
<OutletContainer>
<Outlet />
</OutletContainer>
...
<Toaster />
</CartProvider>
...
);
}
创建产品页面
在本节中,您将创建一个产品页面。部署到生产环境时,您无法为每个产品创建单独的页面,因此您需要创建一个动态页面,该页面将根据产品的版本号运行handle。在 Remix 中,您将动态页面命名为 `<div>` $slug.tsx。
你需要handle从页面的 URL 中获取信息,你可以使用加载函数来实现,但使用useParams钩子函数会更简单。
app/routes/products/$slug.tsx创建包含以下内容的文件:
import { useParams } from '@remix-run/react';
import { useCart, useCreateLineItem, useProducts } from 'medusa-react';
export default function ProductSlug() {
const { slug } = useParams();
}
slug是从您的 URL 获取页面的 slug,例如,localhost:3000/products/sweatshirtslug 是(记住您在组件中sweatshirt传递了)。handleProductCard
接下来,使用useProducts钩子从 Medusa 获取您的产品并将其添加到 UI 中:
export default function ProductSlug() {
...
const { products } = useProducts(
{
handle: slug,
},
{}
);
if (!products) {
return <div></div>; // you can use skeleton loader here instead.
}
const product = products[0];
return (
<div className="flex flex-col items-center lg:justify-between lg:flex-row px-10 sm:px-20 md:px-44 pt-44 max-w-[100rem] flex-grow w-screen">
<img src={product.thumbnail!} className="h-96 w-auto" />
<div>
<h1 className="text-4xl pb-10 text-white">{product.title}</h1>
<p className="w-72">{product.description}</p>
</div>
</div>
);
}
这里useProducts使用了钩子函数并传递了别名。产品加载时,显示一个空的div(您也可以使用骨架加载器)。
最后,使用钩子返回的第一个项目,useProducts即页面 URL 中包含句柄的产品。
请注意,此处使用的标题和描述数据来自 Medusa 服务器,因为 Strapi 插件支持双向同步。因此,您在 Strapi 中对产品进行的任何更改都会同步到 Medusa 服务器。您也可以选择显示来自 Strapi 的 CMS 产品数据。
您还需要根据客户所在地区显示价格。为此,请创建以下文件app/lib/formatPrice.ts:
import { formatVariantPrice } from 'medusa-react';
import type { Cart } from "medusa-react/dist/types";
import type { ProductVariant } from '@medusajs/medusa';
export const formatPrice = (variant: ProductVariant, cart: Cart) => {
if (cart)
return formatVariantPrice({
variant: variant,
region: cart.region,
});
};
formatVariantPrice您可以在此处使用该函数medusa-react。这将根据用户所在地区和所选产品变体对价格进行格式化。
然后,在以下情况下使用它app/routes/products/$slug.tsx:
import { formatPrice } from '~/lib/formatPrice';
export default ProductSlug() {
...
const { cart } = useCart();
return (
<div className="flex flex-col items-center lg:justify-between lg:flex-row px-10 pb-44 sm:px-20 md:px-44 pt-44 max-w-[100rem] flex-grow w-screen">
<img src={product.thumbnail!} className="h-96 w-auto" />
<div>
<h1 className="text-4xl pt-5 lg:pt-0 pb-5 lg:pb-10 text-white">
{product.title}
</h1>
<p className="w-72">{product.description}</p>
<p className="text-xl text-white pt-5">
{formatPrice(product.variants[0], cart)}
</p>
</div>
</div>
)
}
接下来,创建一个功能,用于将商品添加到购物车并推送通知。
import toast from 'react-hot-toast';
export default function ProductSlug() {
...
const { mutate } = useCreateLineItem(cart?.id!);
const addItem = () => {
mutate(
{
variant_id: products?.slice(0, 1)[0].variants[0].id!,
quantity: 1,
},
{
onSuccess: () => {
toast('Added to Cart!');
},
}
);
};
...
}
该useCreateLineItem钩子函数允许您添加商品。它需要购物车 ID。该addItem函数会将商品添加到购物车,然后显示一条弹出通知。
在返回的 JSX 中添加一个按钮,点击该按钮即可运行此函数:
export default function ProductSlug() {
...
return (
<div className="flex flex-col items-center lg:justify-between lg:flex-row px-10 sm:px-20 md:px-44 pt-44 max-w-[100rem] flex-grow w-screen">
<img src={product.thumbnail!} className="h-96 w-auto" />
<div>
<h1 className="text-4xl pb-10 text-white">{product.title}</h1>
<p className="w-72">{product.description}</p>
<p className="text-xl text-white pt-5">
{formatPrice(product.variants[0])}
</p>
<button
className="p-5 rounded-md w-full bg-slate-400 bg-opacity-25 mt-10 cursor-pointer active:scale-95 transition ease-in-out duration-75"
onClick={() => addItem()}
>
Add item
</button>
</div>
</div>
);
}
最后一步是添加导航栏,以便轻松导航至购物车。
app/components/topNavigator.tsx创建包含以下内容的文件:
import { Link } from '@remix-run/react';
export default function TopNavigator() {
return (
<nav className="flex w-screen fixed top-0 right-0 left-0 items-center py-4 flex-row justify-between px-10 sm:px-20 md:px-44 z-10 bg-black">
<Link to="/" className="text-xl">
MRS
</Link>
<Link to="/cart">Cart</Link>
</nav>
);
}
将此组件添加TopNavigator到您的代码中,使其显示在所有页面上。将其添加到`:`root.tsx标签上方。Outlet
import TopNavigator from './components/topNavigator';
export default function App() {
return (
...
<CartProvider>
<OutletContainer>
<TopNavigator />
<Outlet />
</OutletContainer>
...
</CartProvider>
...
);
}
测试产品页面
要测试您的产品页面,请重新启动您的 Remix 服务器(确保 Strapi 和 Medusa 服务器已在运行)。
点击主页上的任何产品,即可查看详细信息。
创建购物车页面
现在,您将创建最终的购物车页面。
app/routes/cart.tsx创建包含以下内容的文件:
import { useState, useEffect } from "react";
import { medusaClient } from "~/lib/config";
import type { Cart as CartType } from "medusa-react/dist/types";
export default function Cart() {
const [cart, setCart] = useState<CartType>();
useEffect(() => {
medusaClient.carts
.retrieve(localStorage.getItem("cart_id")!)
.then(({ cart }) => {
setCart(cart);
});
}, [cart]);
return (
<div className="px-10 sm:px-20 md:px-44 pt-44 max-w-[100rem] flex-grow w-screen">
{cart?.items.map((variant) => (
<div
key={variant.id}
className="flex flex-col xl:flex-row h-64 my-10 space-x-8 space-y-4 items-center"
>
<img className="h-full" src={variant.thumbnail!} />
<div>
<h3 className="pt-2 text-white text-xl">{variant.title}</h3>
<p className="text-slate-400">{variant.quantity}</p>
</div>
</div>
))}
</div>
);
}
cart.items是一个包含顾客购物车中所有商品的数组。您需要显示每个商品的缩略图、标题和数量。
测试购物车页面
重启 Remix 服务器(确保 Strapi 和 Medusa 服务器已在运行)。将商品添加到购物车后,它将显示在购物车页面上。
结论
通过本教程,您可以了解使用完全开源的工具构建电子商务商店是多么容易。
还有很多方法可以改进您的店铺页面,例如:
- 改进用户界面
- 改进购物车页面,增加移除或更新商品等功能。
- 实施结账流程
- 集成Stripe 插件进行支付。
- 安装 Medusa Admin来管理订单、产品等等。
- 请查看Medusa 的 Storefront API参考文档,了解您可以在 Storefront 中执行的更多操作。
文章来源:https://dev.to/medusajs/remix-shopify-circumvent-shopifys-apis-and-go-open-source-36g2如果您对 Medusa 有任何疑问或问题,请随时通过Discord联系 Medusa 团队 。



















