Tillbaks till resurser

Teknisk utforskning: Skapa dynamiska storefronts för e-handel med Shopify Hydrogen

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.

Teknisk utforskning: Skapa dynamiska storefronts för e-handel med Shopify Hydrogen

Bildkredit: Bild av @__matthoffman__ frÄn Unsplash

Shopify HydrogenRemixHeadless CommerceE-commerce
Av Kenny TranPublicerades den 4/8/2025Uppdaterades senast den 4/8/2025

Introduction

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.

Vad Àr Shopify Hydrogen?

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:

  • Headless arkitektur: Separerar anvĂ€ndargrĂ€nssnittet frĂ„n backend för full designkontroll.
  • Prestanda: SSR, React Server Components och snabb dataladdning sĂ€kerstĂ€ller sidladdningar pĂ„ under en sekund.
  • Anpassning: ÅteranvĂ€ndbara komponenter och API:er för unik design.
  • Oxygen Hosting: Shopifys globala CDN för sömlös driftsĂ€ttning.
  • SEO-verktyg: Autogenererade sitemaps och metadata för att öka den organiska trafiken.

Hydrogen driver butiker som Lady Gagas kÀndisbutik och Manors butik för golfklÀder och levererar skalbara, personliga upplevelser.

Varför det Àr relevant

Shopify Hydrogen kombinerar utvecklingseffektivitet med affÀrseffekt och omdefinierar e-handeln.

  • För Företag: Det ger snabba, personliga storefronts med SEO.
  • För Utvecklare: Komponenter och hooks som förenklar datahĂ€mtning, medan Remix och Vite möjliggör snabb iteration.
  • Branschtrend: Headless commerce vĂ€xer, och 2024 kommer varumĂ€rken att prioritera snabbhet och flexibilitet.

VÄr provkörning

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.

Projekt UppsÀttning

Vi initierade ett Hydrogen-projekt med Remix App Router och satte upp Shopify-integration:

  1. Miljö UppsÀttning:
  • Skapade en Hydrogen-app: npm create @shopify/hydrogen@latest -- --template hello-world.
  • Installerade dependencies: npm i @shopify/hydrogen @shopify/remix-oxygen @shopify/hydrogen-ui vite tailwindcss.
  • StĂ€llde in Shopify Storefront API-token i .env:
PUBLIC_STOREFRONT_API_TOKEN=shpat_xxx
PUBLIC_SHOPIFY_DOMAIN=myshop.myshopify.com
  1. Hydrogen Konfiguration:
  • AnvĂ€nde Remix för routing och SSR, med Vite för byte av varm modul.
  • LĂ€nkad till en Shopify-butik via appen Hydrogen försĂ€ljningskanal.
  • Konfigurerade Tailwind i tailwind.config.js och app/tailwind.css:
@tailwind base;
@tailwind components;
@tailwind utilities;

AnvÀndningsfall 1: Produktsida

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
          }
        }
      }
    }
  }
`;

AnvÀndningsfall 2: Kundvagnssida med Add-to-Cart

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>

AnvÀndningsfall 3: Söksida med filtrering

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
            }
          }
        }
      }
    }
  }
`;

Referenser

  1. https://shopify.dev/docs/api/hydrogen
  2. https://shopify.engineering/how-we-built-hydrogen
  3. https://hydrogen.shopify.dev/updates