What is HydrogenAnchor
Hydrogen is a React-based JavaScript framework developed by Shopify. Described as a “Framework for Dynamic Commerce”, using Shopify’s Hydrogen gives you the ability to build and deliver fast, personalized shopping experiences.
Explore the official documentation or view the repo to get started with your next Hydrogen project.
To get started with a new Hydrogen app, simply run the CLI:
npx create-hydrogen-app
Why use HydrogenAnchor
Hydrogen allows for faster, personalized online shopping experiences by integrating React Server Components, smart caching, and streaming server-side rendering - unlocking fast first-renders and progressive hydration.
When paired with the globally distributed Shopify GraphQL Storefront API and commerce components, it’s a blazingly fast framework.
Hydrogen provides better builds with hot reloading, built-in ESM, and a dev environment powered by Typescript, Vite, and TailwindCSS.
View Hydrogen in action by exploring Shopify’s implementation
Headless CMS for Hydrogen AppsAnchor
Why use a Headless CMS for hydrogen projects? Often companies want to combine the capabilities of a Headless CMS or a Content API with the eCommerce functionality that Shopify offers.
When handling product inventories and global shopfronts with Shopify, your content needs to be served equally fast. Hygraph provides you with instant GraphQL Content APIs for GraphQL Queries and GraphQL Mutations, making it an incredibly performant combination with Shopify’s globally distributed APIs.
Working with Shopify and HygraphAnchor
Its easy to use Hygraph as a Shopify Headless CMS by integrating Shopify with UI Extensions.
UI Extensions allow you to extend the functionality of the Hygraph content editing experience by running custom applications within the CMS. UI Extensions can be used for a variety of use-cases, such as adding custom fields to let editors search and pick products from a Shopify catalog. Hygraph, GraphQL, and Shopify are a powerful combination for high-performance eCommerce platforms.
With your frontend powered by Hydrogen and Hygraph, make it easier for your content team by setting up a UI Extension within Hygraph to pick Shopify products.
Use our Shopify UI Extension example to create your own.
All you’d need are an index.tsx
and a picker.tsx
to create your own UI Extenion, and once you have it hosted, you can load your UI Extension into Hygraph.
Here’s our example when integrating Hygraph with Shopify:
Create an index.tsx
import * as React from "react";import "@shopify/polaris/dist/styles.css";import {Wrapper as ExtensionWrapper,useUiExtension,ExtensionDeclaration,FieldExtensionType,FieldExtensionFeature,} from "@hygraph/uix-react-sdk";import enTranslations from "@shopify/polaris/locales/en.json";import {AppProvider,TextField,Button,Spinner,MediaCard,Card,Thumbnail,DisplayText,} from "@shopify/polaris";const extensionDeclaration: ExtensionDeclaration = {extensionType: "field",name: "Shopify Product Picker",fieldType: FieldExtensionType.STRING,features: [FieldExtensionFeature.FieldRenderer],config: {STORE: {type: "string",displayName: "Store ID",required: true,},ACCESS_TOKEN: {type: "string",displayName: "Access Token",required: true,},},};export default function ShopifyExtension({ extensionUid }) {console.log({ extensionUid });if (typeof extensionUid !== "string") return <p> missing extension UID</p>;return (<ExtensionWrapper uid={extensionUid} declaration={extensionDeclaration}><ShopifyProductInput /></ExtensionWrapper>);}/// ok let's make an extension out of thisfunction ShopifyProductInput() {const {value,onChange,extension: {config: { STORE, ACCESS_TOKEN },},} = useUiExtension();React.useEffect(() => {let listener;const listenForItem = async function () {const postRobot = (await import("post-robot")).default;listener = postRobot.on("selectItem", (event) => {const id = event.data.id;setTimeout(() => onChange(id), 100);return true;});};listenForItem();return () => {listener.cancel();};}, []);const openPicker = React.useCallback(() => {const windowFeatures ="menubar=yes,resizable=yes,scrollbars=yes,status=yes,width=320,height=640";const pickerWindow = window.open("shopify/picker","Shopify_Picker",windowFeatures);let configInterval;const sendConfig = async () => {const postRobot = (await import("post-robot")).default;postRobot.send(pickerWindow, "config", { STORE, ACCESS_TOKEN }).then(function (result) {console.log({ result });if (result) clearInterval(configInterval);});};configInterval = setInterval(sendConfig, 200);}, []);return (<AppProvider i18n={enTranslations}><div><TextFieldvalue={value}label={undefined}onChange={onChange}connectedRight={<Button onClick={openPicker}>open picker</Button>}/><ProductPreviewproductId={value}store={STORE}accessToken={ACCESS_TOKEN}/></div></AppProvider>);}function useThrottledfunction(callback, delay) {const savedCallback = React.useRef();const savedArgs = React.useRef([]);const timeoutId = React.useRef(null);React.useEffect(() => (savedCallback.current = callback), [callback]);const dummyFunction = React.useCallback((...args) => {if (timeoutId.current) {clearTimeout(timeoutId.current);}timeoutId.current = setTimeout(() => {//@ts-ignoresavedCallback.current(...savedArgs.current);}, delay);savedArgs.current = args;},[callback]);return dummyFunction;}function ProductPreview({productId,store,accessToken,}: {productId: string;store: string;accessToken: string;}) {const [state, setState] = React.useState({loading: false,error: null,data: null,});const fetchProduct = React.useCallback((productId) => {if (typeof window !== "undefined") {setState({ loading: true, error: null, data: null });window.fetch(`/api/extensions/shopify/product/${encodeURIComponent(productId)}`,{method: "GET",headers: {"X-Shopify-Store": store,"X-Shopify-Access-Token": accessToken,},}).then((res) => {if (res.ok) {res.json().then((data) => {if (data.data.product)setState({loading: false,error: null,data: data?.data?.product,});elsesetState({loading: false,error: new Error("Product not found"),data: null,});});} else {setState({loading: false,error: new Error(res.statusText),data: null,});}}).catch((error) => setState({ loading: false, error, data: null }));}},[store, accessToken]);const throttledFetchProduct = useThrottledfunction(fetchProduct, 100);React.useEffect(() => {if (productId.startsWith("gid://shopify/Product/"))throttledFetchProduct(productId);elsesetState({loading: false,error: null,data: null,});}, [productId]);return state.loading ? (<Card><Spinner /></Card>) : (<React.Fragment>{state.data && (<div style={{ padding: "12px" }}><Card><div style={{ display: "flex" }}><Thumbnailsource={state.data.featuredImage?.transformedSrc}alt="product image"size="medium"/><DisplayText size="small">{state.data.title}</DisplayText></div></Card></div>)}{state.error && <p style={{ color: "red" }}>{state.error.message}</p>}</React.Fragment>);}
And configure your picker.tsx
import * as React from "react";import "@shopify/polaris/dist/styles.css";import enTranslations from "@shopify/polaris/locales/en.json";import {AppProvider,Page,ResourceList,ResourceItem,Thumbnail,Spinner,Badge,} from "@shopify/polaris";export default function ShopifyPicker({ extensionUid }) {const [config, setConfig] = React.useState({STORE: null,ACCESS_TOKEN: null,});React.useEffect(() => {let listener;const listenForItem = async function () {const postRobot = (await import("post-robot")).default;listener = postRobot.on("config", (event) => {const { STORE, ACCESS_TOKEN } = event.data;setTimeout(() => setConfig({ STORE, ACCESS_TOKEN }), 100);return true;});};listenForItem();return () => {listener.cancel();};}, []);if (typeof config.STORE !== "string") return <p></p>;return (<ProductPicker store={config.STORE} accessToken={config.ACCESS_TOKEN} />);}function ProductPicker({ store, accessToken }) {const { data, loading, error } = useFetchProducts({store,accessToken,});return (<AppProvider i18n={enTranslations}><Page title="Product picker">{loading && (<React.Fragment><Spinner /></React.Fragment>)}{data && (<ResourceListitems={data}renderItem={(item: any) => {const { id, title, status, featuredImage } = item;return (<ResourceItemid={id}onClick={async () => {const PostRobot = (await import("post-robot")).default;PostRobot.send(window.opener, "selectItem", { id }).then(function () {window.close();});}}media={<Thumbnailsource={featuredImage?.transformedSrc}alt="avatar"size="small"/>}>{title}{" "}<Badgestatus={status === "ACTIVE"? "success": status === "DRAFT"? "warning": undefined}>{status}</Badge></ResourceItem>);}}/>)}</Page></AppProvider>);}function useFetchProducts({ store, accessToken }) {const [state, setState] = React.useState({loading: true,error: null,data: null,});React.useEffect(() => {if (typeof window !== "undefined") {window.fetch("/api/extensions/shopify/products", {method: "GET",headers: {"X-Shopify-Store": store,"X-Shopify-Access-Token": accessToken,},}).then((res) => {if (res.ok) {return res.json();} else {throw new Error(`Failed fetching products: ${res.status} ${res.body}`);}}).then((data) => {setState({loading: false,error: null,data: data.data.products.edges.map((e) => e.node),});}).catch((error) => setState({ loading: false, error, data: null }));}}, []);return state;}
That’s it! You’re set for your content team to work with Hygraph and Shopify.