VÀlkommen till Tech Exploration, dÀr Ketryon testar innovativa verktyg för att driva moderna lösningar. I den hÀr utgÄvan dyker vi in i att bygga headless e-handelsbutiker med Shopify Hydrogen.
Bildkredit: Bild av @__matthoffman__ frÄn Unsplash
PÄ Ketryon brinner vi för verktyg som gör det möjligt för företag att trivas med e-handel. DÀrför bestÀmde vi oss för att utforska Shopify Hydrogen, ett React-baserat ramverk för att bygga anpassade, Shopify-storefronts. Till skillnad frÄn Liquids stela mallar frikopplar Hydrogen frontend för dynamiska produktsidor, kundvagnar och sökfunktioner.
Shopify Hydrogen Àr ett React-baserat ramverk med öppen kÀllkod för att bygga anpassade Shopify-storefronts, utformat för e-handel. Det anvÀnder Shopifys Storefront API för att hÀmta produkter, kundvagnar och kunddata, i kombination med Remix för server-side rendering (SSR) och routing. Hydrogen innehÄller komponenter (t.ex. ShopPayButton
, CartLineItem
), hooks (t.ex. useShopQuery
, useCart
), Tailwind CSS för styling och Vite för snabb utveckling.
Viktiga egenskaper:
Hydrogen driver butiker som Lady Gagas kÀndisbutik och Manors butik för golfklÀder och levererar skalbara, personliga upplevelser.
Shopify Hydrogen kombinerar utvecklingseffektivitet med affÀrseffekt och omdefinierar e-handeln.
För att utforska Shopify Hydrogens mÄngsidighet byggde vi ett demo för en lÄtsasbutik med sneakers och visade upp tre anvÀndningsfall: en produktsida, en varukorgssida med funktion för att lÀgga till i varukorgen och en söksida med filtrering.
Vi initierade ett Hydrogen-projekt med Remix App Router och satte upp Shopify-integration:
npm create @shopify/hydrogen@latest -- --template hello-world
.npm i @shopify/hydrogen @shopify/remix-oxygen @shopify/hydrogen-ui vite tailwindcss
..env
:PUBLIC_STOREFRONT_API_TOKEN=shpat_xxx PUBLIC_SHOPIFY_DOMAIN=myshop.myshopify.com
tailwind.config.js
och app/tailwind.css
:@tailwind base; @tailwind components; @tailwind utilities;
Vi byggde en produktsida i app/routes/products.$handle.tsx
, med hjÀlp av useShopQuery
för att hÀmta data och ShopPayButton
för betalningar, med fallback-logik för bilder/varianter:
import { defer } from "@shopify/remix-oxygen"; import { useLoaderData, Link } from "@remix-run/react"; import { ShopPayButton, Image } from "@shopify/hydrogen"; import { useState } from "react"; export async function loader({ params, context }) { const { handle } = params; const { storefront } = context; const { product } = await storefront.query(PRODUCT_QUERY, { variables: { handle }, }); return defer({ product, shop: context.shop }); } export default function ProductPage() { const { product, shop } = useLoaderData(); const [consent, setConsent] = useState(false); const variant = product.variants.nodes[0] || {}; const image = product.images.nodes[0] || { url: "/placeholder.jpg" }; const variantId = variant.id; return ( <div className="max-w-4xl mx-auto p-6"> <h1 className="text-3xl font-bold">{product.title}</h1> <div className="grid grid-cols-2 gap-6 mt-6"> <Image data={image} className="w-full" /> <div> <p className="text-lg">{product.description}</p> <p className="text-2xl font-semibold mt-4"> ${variant.price?.amount || "N/A"} </p> <label className="block mt-4"> <input type="checkbox" checked={consent} onChange={(e) => setConsent(e.target.checked)} /> I agree to the <Link to="/privacy">privacy policy</Link>. </label> {variantId && ( <div className="mt-4"> <ShopPayButton variantIds={[variantId]} storeDomain={shop.domain} disabled={!consent} /> </div> )} </div> </div> </div> ); } const PRODUCT_QUERY = `#graphql query ($handle: String!) { product(handle: $handle) { id title description images(first: 1) { nodes { url } } variants(first: 1) { nodes { id price { amount } } } } } `;
Vi byggde en kundvagnssida i app/routes/cart.tsx
, med hjÀlp av useCart
för att hantera add-to-cart-funktionalitet och CartLineItem
för rendering:
import { useCart, CartLineItem, CartCheckoutButton } from "@shopify/hydrogen"; import { json } from "@shopify/remix-oxygen"; import { Link } from "@remix-run/react"; export async function action({ request, context }) { const { cart } = context; const formData = await request.formData(); const variantId = formData.get("variantId"); await cart.addLines([{ merchandiseId: variantId, quantity: 1 }]); return json({ status: "success" }); } export default function CartPage() { const { lines } = useCart(); return ( <div className="max-w-4xl mx-auto p-6"> <h1 className="text-3xl font-bold">Your Cart</h1> {lines.length === 0 ? ( <p className="mt-6"> Your cart is empty.{" "} <Link to="/products" className="text-blue-500"> Shop now </Link> . </p> ) : ( <div className="mt-6"> {lines.map((line) => ( <CartLineItem key={line.id} line={line} /> ))} <CartCheckoutButton className="mt-4 bg-blue-500 text-white px-6 py-2 rounded" /> </div> )} </div> ); }
Vi lade till en âLĂ€gg i kundvagnâ-knapp pĂ„ produktsidan genom att Ă€ndra app/routes/products.$handle.tsx
:
// Add to existing ProductPage component <form method="POST" action="/cart" className="mt-4"> <input type="hidden" name="variantId" value={variantId} /> <button type="submit" disabled={!consent || !variantId} className="bg-green-500 text-white px-6 py-2 rounded" > Add to Cart </button> </form>
Vi byggde en söksida i app/routes/search.tsx
och anvÀnde useShopQuery
för att hÀmta produkter med filter (t.ex. pris, kategori):
import { defer } from "@shopify/remix-oxygen"; import { useLoaderData, Link } from "@remix-run/react"; import { Image } from "@shopify/hydrogen"; import { useState } from "react"; export async function loader({ context, request }) { const { storefront } = context; const url = new URL(request.url); const searchTerm = url.searchParams.get("q") || ""; const { products } = await storefront.query(SEARCH_QUERY, { variables: { query: searchTerm, first: 10 }, }); return defer({ products, searchTerm }); } export default function SearchPage() { const { products, searchTerm } = useLoaderData(); const [query, setQuery] = useState(searchTerm); return ( <div className="max-w-4xl mx-auto p-6"> <h1 className="text-3xl font-bold">Search Products</h1> <form className="mt-6"> <input type="text" value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search sneakers..." className="w-full p-2 border rounded" /> <button type="submit" formAction="/search" className="mt-2 bg-blue-500 text-white px-4 py-2 rounded" > Search </button> </form> <div className="grid grid-cols-3 gap-4 mt-6"> {products.nodes.map((product) => ( <Link key={product.id} to={`/products/${product.handle}`} className="border p-4 rounded" > <Image data={product.images.nodes[0] || { url: "/placeholder.jpg" }} className="w-full h-48 object-cover" /> <h3 className="text-lg font-semibold mt-2">{product.title}</h3> <p className="text-gray-600"> ${product.variants.nodes[0]?.price.amount || "N/A"} </p> </Link> ))} </div> </div> ); } const SEARCH_QUERY = `#graphql query ($query: String!, $first: Int!) { products(query: $query, first: $first) { nodes { id handle title images(first: 1) { nodes { url } } variants(first: 1) { nodes { price { amount } } } } } } `;
VÀlkommen till Tech Exploration, dÀr Ketryon testar innovativa verktyg för att driva moderna lösningar. I den hÀr utgÄvan dyker vi in i att bygga AI-drivna applikationer med Vercel AI SDK.
VÀlkommen till Tech Exploration, dÀr Ketryon testar innovativa verktyg för att driva moderna lösningar. I den hÀr utgÄvan dyker vi in i automatiserad e-postmarknadsföring med Klaviyo och Zapier.