Skip to content

Latest commit

 

History

History
1573 lines (1077 loc) · 71.5 KB

CHANGELOG.md

File metadata and controls

1573 lines (1077 loc) · 71.5 KB

@shopify/hydrogen

2024.10.0

Patch Changes

  • Add optional headers param for logout redirect (#2602) by @coryagami

  • Stabilize getSitemap, getSitemapIndex and implement on skeleton (#2589) by @juanpprieto

    1. Update the getSitemapIndex at /app/routes/[sitemap.xml].tsx
    - import {unstable__getSitemapIndex as getSitemapIndex} from '@shopify/hydrogen';
    + import {getSitemapIndex} from '@shopify/hydrogen';
    1. Update the getSitemap at /app/routes/sitemap.$type.$page[.xml].tsx
    - import {unstable__getSitemap as getSitemap} from '@shopify/hydrogen';
    + import {getSitemap} from '@shopify/hydrogen';

    For a reference implementation please see the skeleton template sitemap routes

  • Update <ProductPrice> to remove deprecated code usage for priceV2 and compareAtPriceV2. Remove export for getCustomerPrivacy. (#2601) by @wizardlyhel

  • [Breaking change] (#2588) by @wizardlyhel

    Set up Customer Privacy without the Shopify's cookie banner by default.

    If you are using Shopify's cookie banner to handle user consent in your app, you need to set withPrivacyBanner: true to the consent config. Without this update, the Shopify cookie banner will not appear.

      return defer({
        ...
        consent: {
          checkoutDomain: env.PUBLIC_CHECKOUT_DOMAIN,
          storefrontAccessToken: env.PUBLIC_STOREFRONT_API_TOKEN,
    +      withPrivacyBanner: true,
          // localize the privacy banner
          country: args.context.storefront.i18n.country,
          language: args.context.storefront.i18n.language,
        },
      });
  • Update to 2024-10 SFAPI (#2570) by @wizardlyhel

  • [Breaking change] (#2546) by @frandiox

    Update createWithCache to make it harder to accidentally cache undesired results. request is now mandatory prop when initializing createWithCache.

    // server.ts
    export default {
      async fetch(
        request: Request,
        env: Env,
        executionContext: ExecutionContext,
      ): Promise<Response> {
        try {
          // ...
    -     const withCache = createWithCache({cache, waitUntil});
    +     const withCache = createWithCache({cache, waitUntil, request});

    createWithCache now returns an object with two utility functions: withCache.run and withCache.fetch. Both have a new prop shouldCacheResult that must be defined.

    The original withCache callback function is now withCache.run. This is useful to run multiple fetch calls and merge their responses, or run any arbitrary code. It caches anything you return, but you can throw if you don't want to cache anything.

      const withCache = createWithCache({cache, waitUntil, request});
    
      const fetchMyCMS = (query) => {
    -    return withCache(['my-cms', query], CacheLong(), async (params) => {
    +    return withCache.run({
    +      cacheKey: ['my-cms', query],
    +      cacheStrategy: CacheLong(),
    +      // Cache if there are no data errors or a specific data that make this result not suited for caching
    +      shouldCacheResult: (result) => !result?.errors,
    +    }, async(params) => {
          const response = await fetch('my-cms.com/api', {
            method: 'POST',
            body: query,
          });
          if (!response.ok) throw new Error(response.statusText);
          const {data, error} = await response.json();
          if (error || !data) throw new Error(error ?? 'Missing data');
          params.addDebugData({displayName: 'My CMS query', response});
          return data;
        });
      };

    New withCache.fetch is for caching simple fetch requests. This method caches the responses if they are OK responses, and you can pass shouldCacheResponse, cacheKey, etc. to modify behavior. data is the consumed body of the response (we need to consume to cache it).

    const withCache = createWithCache({cache, waitUntil, request});
    
    const {data, response} = await withCache.fetch<{data: T; error: string}>(
      'my-cms.com/api',
      {
        method: 'POST',
        headers: {'Content-type': 'application/json'},
        body,
      },
      {
        cacheStrategy: CacheLong(),
        // Cache if there are no data errors or a specific data that make this result not suited for caching
        shouldCacheResponse: (result) => !result?.error,
        cacheKey: ['my-cms', body],
        displayName: 'My CMS query',
      },
    );
  • [Breaking change] (#2585) by @wizardlyhel

    Deprecate usages of product.options.values and use product.options.optionValues instead.

    1. Update your product graphql query to use the new optionValues field.
      const PRODUCT_FRAGMENT = `#graphql
        fragment Product on Product {
          id
          title
          options {
            name
    -        values
    +        optionValues {
    +          name
    +        }
          }
    1. Update your <VariantSelector> to use the new optionValues field.
      <VariantSelector
        handle={product.handle}
    -    options={product.options.filter((option) => option.values.length > 1)}
    +    options={product.options.filter((option) => option.optionValues.length > 1)}
        variants={variants}
      >
  • Add utility functions decodeEncodedVariant and isOptionValueCombinationInEncodedVariant for parsing product.encodedVariantExistence and product.encodedVariantAvailability fields. (#2425) by @lhoffbeck

  • [Breaking change] (#2572) by @wizardlyhel

    Update all cart mutation methods from createCartHandler to return cart warnings.

    As of API version 2024-10, inventory errors about stock levels will no longer be included in the userErrors of cart mutations. Inventory errors will now be available in a new return field warnings and will contain explicit code values of MERCHANDISE_NOT_ENOUGH_STOCK or MERCHANDISE_OUT_OF_STOCK. Reference: https://shopify.dev/changelog/cart-warnings-in-storefront-api-cart

  • Updated dependencies [8c89f298, 84a66b1e, 76cd4f9b]:

2024.7.9

Patch Changes

2024.7.8

Patch Changes

2024.7.7

Patch Changes

  • Fix Shopify.customerPrivacy.setTrackingConsent override not working properly (#2538) by @wizardlyhel

2024.7.6

Patch Changes

  • Emit a document event shopifyCustomerPrivacyApiLoaded when Customer Privacy API is ready and fix analytics events sending to Shopify. (#2528) by @wizardlyhel

  • Updated dependencies [d0ff37a9]:

2024.7.5

Patch Changes

2024.7.4

Patch Changes

  • Fix the Script component to not throw when using it for inline scripts with dangerouslySetInnerHTML (#2428) by @blittle

2024.7.3

Patch Changes

  • Prevent sending analytics data to Shopify when Chrome-Lighthouse user agent is detected (#2401) by @wizardlyhel

  • Create createHydrogenContext that combined createStorefrontClient, createCustomerAccountClient and createCartHandler. (#2333) by @michenly

  • Add a waitForHydration prop to the Script component to delay loading until after hydration. This fixes third-party scripts that modify the DOM and cause hydration errors. (#2389) by @blittle

    Note: For security, nonce is not supported when using waitForHydration. Instead you need to add the domain of the script directly to your Content Securitiy Policy directives.

  • Fix the OptimisticCart type to properly retain the generic of line items. The OptimisticCartLine type now takes a cart or cart line item generic. (#2327) by @blittle

  • Export ShopAnalytics type (#2384) by @Braedencraig

  • Updated dependencies [cfbfc827, b09e9a4c]:

2024.7.2

Patch Changes

  • Fix subrequest profiler by removing the Layout export from virtual root. (#2344) by @michenly

2024.7.1

Patch Changes

  • [Breaking change] (#2137) by @michenly

    customerAccount no longer commit session automatically.

  • [Breaking change] (#2113) by @blittle

    Previously the VariantSelector component would filter out options that only had one value. This is undesireable for some apps. We've removed that filter, if you'd like to retain the existing functionality, simply filter the options prop before it is passed to the VariantSelector component:

     <VariantSelector
       handle={product.handle}
    +  options={product.options.filter((option) => option.values.length > 1)}
    -  options={product.options}
       variants={variants}>
     </VariantSelector>

    Fixes #1198

  • Fix the types for optimistic cart (#2132) by @blittle

  • Improve the types for useOptimisticCart() (#2269) by @blittle

  • Fix a small rounding issue when checking stale-while-revalidate timing. (#2220) by @frandiox

  • Update virtual route to use Layout component in the root file. (#2292) by @michenly

  • Add sellingPlanId support to BuyNowButton. (#2254) by @dvisockas

  • Fix customData from Analytics.Provider not being passed to page view events (#2224) by @wizardlyhel

  • Auto cookie domain detection for customer privacy api and better error message for missing analytics fields (#2256) by @wizardlyhel

  • [New Features] (#2183) by @blittle

    Add a useOptimisticVariant hook for optimistically rendering product variant changes. This makes switching product variants instantaneous. Example usage:

    function Product() {
      const {product, variants} = useLoaderData<typeof loader>();
    
      // The selectedVariant optimistically changes during page
      // transitions with one of the preloaded product variants
      const selectedVariant = useOptimisticVariant(
        product.selectedVariant,
        variants,
      );
    
      return <ProductMain selectedVariant={selectedVariant} />;
    }

    This also introduces a small breaking change to the VariantSelector component, which now immediately updates which variant is active. If you'd like to retain the current functionality, and have the VariantSelector wait for the page navigation to complete before updating, use the waitForNavigation prop:

    <VariantSelector
      handle={product.handle}
      options={product.options}
      waitForNavigation
    >
      ...
    </VariantSelector>
  • Return null instead of empty object from cart.get() when the cart id is invalid. (#2258) by @frandiox

  • Updated dependencies [54c2f7ad]:

2024.4.3

Patch Changes

  • Add the useOptimisticCart() hook. This hook takes the cart object as a parameter, and processes all pending cart actions, locally mutating the cart with optimistic state. An optimistic cart makes cart actions immediately render in the browser while the action syncs to the server. This increases the perceived performance of the application. (#2069) by @blittle

    Example usage:

    // Root loader returns the cart data
    export async function loader({context}: LoaderFunctionArgs) {
      return defer({
        cart: context.cart.get(),
      });
    }
    
    // The cart component renders each line item in the cart.
    export function Cart({cart}) {
      if (!cart?.lines?.nodes?.length) return <p>Nothing in cart</p>;
    
      return cart.lines.nodes.map((line) => (
        <div key={line.id}>
          <Link to={`/products${line.merchandise.product.handle}`}>
            {line.merchandise.product.title}
          </Link>
        </div>
      ));
    }

    The problem with this code is that it can feel slow. If a new item is added to the cart, it won't render until the server action completes and the client revalidates the root loader with the new cart data.

    If we update the cart implementation with a new useOptimisticCart() hook, Hydrogen can take the pending add to cart action, and apply it locally with the existing cart data:

    export function Cart({cart}) {
      const optimisticCart = useOptimisticCart(cart);
    
      if (!optimisticCart?.lines?.nodes?.length) return <p>Nothing in cart</p>;
    
      return optimisticCart.lines.nodes.map((line) => (
        <div key={line.id}>
          <Link to={`/products${line.merchandise.product.handle}`}>
            {line.merchandise.product.title}
          </Link>
        </div>
      ));
    }

    This works automatically with the CartForm.ACTIONS.LinesUpdate and CartForm.ACTIONS.LinesRemove. To make it work with CartForm.Actions.LinesAdd, update the CartForm to include the selectedVariant:

    export function ProductCard({product}) {
      return (
        <div>
          <h2>{product.title}</h2>
          <CartForm
            route="/cart"
            action={CartForm.ACTIONS.LinesAdd}
            inputs={{
              lines: [
                {
                  merchandiseId: product.selectedVariant.id,
                  quantity: 1,
                  // The whole selected variant is not needed on the server, used in
                  // the client to render the product until the server action resolves
                  selectedVariant: product.selectedVariant,
                },
              ],
            }}
          >
            <button type="submit">Add to cart</button>
          </CartForm>
        </div>
      );
    }

    Sometimes line items need to render differently when they have yet to process on the server. A new isOptimistic flag is added to each line item:

    export function Cart({cart}) {
      const optimisticCart = useOptimisticCart(cart);
    
      if (!cart?.lines?.nodes?.length) return <p>Nothing in cart</p>;
    
      return optimisticCart.lines.nodes.map((line) => (
        <div key={line.id} style={{opacity: line.isOptimistic ? 0.8 : 1}}>
          <Link to={`/products${line.merchandise.product.handle}`}>
            {line.merchandise.product.title}
          </Link>
          <CartForm
            route="/cart"
            action={CartForm.ACTIONS.LinesRemove}
            inputs={{lineIds}}
            disabled={line.isOptimistic}
          >
            <button type="submit">Remove</button>
          </CartForm>
        </div>
      ));
    }
  • Adds type support for the script-src-elem directive for CSPs (#2105) by @altonchaney

  • Fix storefrontRedirect to strip trailing slashes when querying for redirects. Resolves #2090 (#2110) by @blittle

  • Ignore /favicon.ico route in Subrequest Profiler. (#2180) by @frandiox

  • Improve errors when a CJS dependency needs to be added to Vite's ssr.optimizeDeps.include. (#2106) by @frandiox

  • <Analytics> and useAnalytics are now stable. (#2141) by @wizardlyhel

  • Improve VariantSelector to return variant object in option values. Thank you @NabeelAhmed1721 by @blittle

  • Fix: Use exiting id_token during Customer Account API token refresh because it does not get return in the API. (#2103) by @juanpprieto

  • Fix optimizing deps when using PNPM. (#2172) by @frandiox

  • Updated dependencies [73716c88, 30d18bdb]:

2024.4.2

Patch Changes

  • Add JSdoc to getSelectedProductOptions utility and cleanup the skeleton implementation (#2089) by @juanpprieto

  • Adding support for B2B to the customer account client and cart handler to store and manage buyer context. Currently Unstable. (#1886) by @dustinfirman

  • When extending the content security policy, if the default directive is 'none' then the default won't be merged into the final directive. (#2076) by @nkgentile

  • Update content-security-policy-builder subdependency to ESM version to avoid preprocessing in Vite. (#2057) by @frandiox

  • Fix Analytics. Provider for error checking and working without privacy banner. (#2025) by @wizardlyhel

  • Updated dependencies [081e1498]:

2024.4.1

Patch Changes

2024.4.0

Minor Changes

  • Change storefrontRedirect to ignore query parameters when matching redirects. For example, a redirect in the admin from /snowboards to /collections/snowboards will now match on the URL /snowboards?utm_campaign=buffer and redirect the user to /collections/snowboards?utm_campaign=buffer. (#1900) by @blittle

    This is a breaking change. If you want to retain the legacy functionality that is query parameter sensitive, pass matchQueryParams to storefrontRedirect():

    storefrontRedirect({
      request,
      response,
      storefront,
      matchQueryParams: true,
    });

Patch Changes

  • Make StorefrontRedirect case insensitive when querying redirect URLs from the Storefront API. (#1941) by @blittle

  • Fix bug where storefrontRedirect would return an error on soft page navigations. (#1880) by @blittle

  • Fix a bug where cart could be null, even though a new cart was created by adding a line item. (#1865) by @blittle

    This allows calling the cart .get() method right after creating a new cart with one of the mutation methods: create(), addLines(), updateDiscountCodes(), updateBuyerIdentity(), updateNote(), updateAttributes(), setMetafields().

    import {
      createCartHandler,
      cartGetIdDefault,
      cartSetIdDefault,
    } from '@shopify/hydrogen';
    
    const cartHandler = createCartHandler({
      storefront,
      getCartId: cartGetIdDefault(request.headers),
      setCartId: cartSetIdDefault(),
      cartQueryFragment: CART_QUERY_FRAGMENT,
      cartMutateFragment: CART_MUTATE_FRAGMENT,
    });
    
    await cartHandler.addLines([{merchandiseId: '...'}]);
    // .get() now returns the cart as expected
    const cart = await cartHandler.get();
  • Add postLogoutRedirectUri option to the Customer Account API client's logout method. (#1871) by @michenly

  • Introducing <UNSTABLE_Analytics.Provider> that also includes Shopify analytics, Customer Privacy API and Privacy banner (#1789) by @wizardlyhel

  • Export new Hydrogen Vite plugin from @shopify/hydrogen/vite. (#1935) by @frandiox

  • Add the customer-account push command to the Hydrogen CLI. This allows you to push the current --dev-origin URL to the Shopify admin to enable secure connection to the Customer Account API for local development. (#1804) by @michenly

  • Fix default content security policy directive for frameAncestors. (#1883) by @blittle

  • Fall back to "mock.shop" when no value is passed in storeDomain to createStorefrontClient in development. (#1971) by @frandiox

  • Allow ui_locale to be passed to the customer account login page. (#1842) by @wizardlyhel

  • Deprecate the <Seo /> component in favor of directly using Remix meta route exports. Add the getSeoMeta to make migration easier. (#1875) by @blittle

    Migration steps:

    1. Remove the <Seo /> component from root.jsx:

     export default function App() {
       const nonce = useNonce();
       const data = useLoaderData<typeof loader>();
    
       return (
         <html lang="en">
           <head>
             <meta charSet="utf-8" />
             <meta name="viewport" content="width=device-width,initial-scale=1" />
    -        <Seo />
             <Meta />
             <Links />
           </head>
           <body>
             <Layout {...data}>
               <Outlet />
             </Layout>
             <ScrollRestoration nonce={nonce} />
             <Scripts nonce={nonce} />
             <LiveReload nonce={nonce} />
           </body>
         </html>
       );
     }
    

    2. Add a Remix meta export to each route that returns an seo property from a loader or handle:

    +import {getSeoMeta} from '@shopify/hydrogen';
    
     export async function loader({context}) {
       const {shop} = await context.storefront.query(`
         query layout {
           shop {
             name
             description
           }
         }
       `);
    
       return {
         seo: {
           title: shop.title,
           description: shop.description,
         },
       };
     }
    
    +export const meta = ({data}) => {
    +   return getSeoMeta(data.seo);
    +};

    3. Merge root route meta data

    If your root route loader also returns an seo property, make sure to merge that data:

    export const meta = ({data, matches}) => {
      return getSeoMeta(
        matches[0].data.seo,
        // the current route seo data overrides the root route data
        data.seo,
      );
    };

    Or more simply:

    export const meta = ({data, matches}) => {
      return getSeoMeta(...matches.map((match) => match.data.seo));
    };

    4. Override meta

    Sometimes getSeoMeta might produce a property in a way you'd like to change. Map over the resulting array to change it. For example, Hydrogen removes query parameters from canonical URLs, add them back:

    export const meta = ({data, location}) => {
      return getSeoMeta(data.seo).map((meta) => {
        if (meta.rel === 'canonical') {
          return {
            ...meta,
            href: meta.href + location.search,
          };
        }
    
        return meta;
      });
    };
  • Updated dependencies [f4d6e5b0, a209019f, e50f4349]:

2024.1.4

Patch Changes

2024.1.3

Patch Changes

2024.1.2

Patch Changes

  • 🐛 Fix issue where customer login does not persist to checkout (#1719) by @michenly

    ✨ Add customerAccount option to createCartHandler. Where a ?logged_in=true will be added to the checkoutUrl for cart query if a customer is logged in.

  • Customer Account API client's query & mutate method now returns errors as an array of GraphQLError(s) that is better formatted. (#1765) by @michenly

    Log GraphQL errors automatically in Customer Account API client, with a new logErrors: boolean option to disable it.

  • Updated dependencies [409e1bca]:

2024.1.1

Patch Changes

  • Add support for multiple schemas in GraphiQL. Fix links in Subrequest Profiler. (#1693) by @frandiox

  • ♻️ CustomerClient type is deprecated and replaced by CustomerAccount (#1692) by @michenly

  • Log GraphQL errors automatically in Storefront client, with a new logErrors: boolean option to disable it. Add back a link to GraphiQL in the error message. (#1690) by @frandiox

2024.1.0

Major Changes

  • Better Hydrogen error handling (#1645) by @wizardlyhel

    • Fix storefront client throwing on partial successful errors
    • Fix subrequest profiler to better display network errors with URL information for Storefront API requests

    Breaking change

    This update changes the shape of the error objects returned by the createCartHandler method.

    Previously, mutations could return an errors array that contained a userErrors array.

    With this change, these arrays are no longer nested. The response can contain both an errors array and a userErrors array. errors contains GraphQL execution errors. userErrors contains errors caused by the cart mutation itself (such as adding a product that has zero inventory).

    storefront.isApiError is deprecated.

    Updated return types for createCartHandler methods

    • cart.get() used to return a Cart type. Now it returns CartReturn type to accommodate the errors object.
    • All other cart methods (ie. cart.addLines) used to return a CartQueryData type. Now it returns CartQueryDataReturn type to accommodate the errors object.
  • Custom rules passed to createContentSecurityPolicy now extend the default Shopify and development domains, instead of overriding them (#1593) by @michenly

  • Upgrade to Storefront API v2024-01 (#1642) by @wizardlyhel

Minor Changes

Patch Changes

  • Fix a bug that allowed undesired redirect to external domains (#1629) by @wizardlyhel

  • Fix content security policy to recognize localhost asset server as a valid source when running the dev command (#1591) by @michenly

  • Fix the <Seo /> component to render canonical URLs without trailing slashes. Thanks to @joshuafredrickson for reporting (#1622) by @blittle

  • Make default HydrogenSession type extensible. (#1590) by @michenly

    Update implementation of HydrogenSession using type:

    import {
    + type HydrogenSession,
    } from '@shopify/hydrogen';
    - class HydrogenSession {
    + class AppSession implements HydrogenSession {
        ...
    }
  • Fix error stack traces thrown by API clients if promises are not awaited (#1656) by @frandiox

  • Updated dependencies [0629bc77, dc8f90de, ca1161b2]:

2023.10.3

Patch Changes

  • Fix the Pagination component to always restore scroll correctly on back/forth navigation. (#1508) by @blittle

  • Serve assets from a separate domain when running the dev server, to better simulate cross-domain behaviors. This makes it more realistic to work with CORS requests, content security policies, and CDN paths in development. (#1503) by @frandiox

  • Export caching types to make creating custom clients easier in TypeScript. (#1507) by @juanpprieto

  • Update the return types of the Customer Account API query and mutation methods. Also update Customer Account API default version to 2024-01. (#1537) by @blittle

  • Fix how peer dependencies are resolved. (#1489) by @frandiox

  • Add default channel value of hydrogen to Hydrogen’s ShopPayButton component. (#1447) by @QuintonC

  • Updated dependencies [848c6260, 62f67873, e8cc49fe]:

2023.10.2

Patch Changes

  • Change @remix-run/server-runtime to properly be a peer dependency by @blittle

2023.10.1

Patch Changes

2023.10.0

Major and Breaking Changes

Remix v2 (#1289) by @frandiox

Hydrogen 2023-10 has upgraded to Remix v2 and is now a peer dependency.

  • Please check the Remix v2 release notes to see what needs to be changed in your app code. Common changes include:

    • Renaming types prefixed with V2_. For example, V2_MetaFunction is now MetaFunction.
    • Renaming other types like LoaderArgs and ActionArgs, which are now LoaderFunctionArgs and ActionFunctionArgs respectively.

    If you were not already using v2 flags, follow the official Remix migration guide before upgrading to v2.

  • Update to Remix v2. Remix is now a peer dependency and its version is no longer pinned. This means that you can upgrade to newer Remix 2.x versions without upgrading Hydrogen. (#1289) by @frandiox

Other breaking changes

  • The default caching strategy has been updated. The new default caching strategy provides a max-age value of 1 second, and a stale-while-revalidate value of 1 day. If you would keep the old caching values, update your queries to use CacheShort: (#1336) by @benjaminsehl

     const {product} = await storefront.query(
       `#graphql
         query Product($handle: String!) {
           product(handle: $handle) { id title }
         }
       `,
       {
         variables: {handle: params.productHandle},
    +    /**
    +     * Override the default caching strategy with the old caching values
    +     */
    +    cache: storefront.CacheShort(),
       },
     );
  • The Storefront API types included are now generated using @graphql-codegen/typescript@4 (changelog). This results in a breaking change if you were importing Scalars directly from @shopify/hydrogen-react or @shopify/hydrogen: (#1108) by @frandiox

     import type {Scalars} from '@shopify/hydrogen/storefront-api-types';
    
     type Props = {
    -  id: Scalars['ID']; // This was a string
    +  id: Scalars['ID']['input']; // Need to access 'input' or 'output' to get the string
     };

Patch Changes

  • Add a client to query the Customer Account API (#1430) by @blittle

  • Update Storefront API version to 2023-10 (#1431) by @wizardlyhel

  • Custom cart methods are now stable: (#1440) by @wizardlyhel

     const cart = createCartHandler({
       storefront,
       getCartId,
       setCartId: cartSetIdDefault(),
    -  customMethods__unstable: {
    +  customMethods: {
         addLines: async (lines, optionalParams) => {
          // ...
         },
       },
     });
  • Remove deprecated parameters and props (#1455 and #1435): (#1435) by @wizardlyhel

    • createStorefrontClient parameters buyerIp and requestGroupId
    • <Image> props loaderOptions and widths
  • Add query explorer plugin to GraphiQL. Start your dev server and load http://localhost:3000/graphiql to use GraphiQL. (#1470) by @frandiox

  • Updated dependencies [0ae7cbe2, ad45656c]:

2023.7.13

Patch Changes

2023.7.12

Patch Changes

  • Move react to peer dependencies. It had been added as a direct dependency by mistake in a previous version. (#1439) by @frandiox

  • Integrate the debug-network tooling with the new --worker-unstable runtime CLI flag. (#1387) by @frandiox

  • Calls to withCache can now be shown in the /debug-network tool when using the Worker runtime. For this to work, use the new request parameter in createWithCache: (#1438) by @frandiox

    export default {
      fetch(request, env, executionContext) {
        // ...
        const withCache = createWithCache({
          cache,
          waitUntil,
    +     request,
        });
        // ...
      },
    }
  • Updated dependencies [d30e2651, 1b45311d, 2627faa7]:

2023.7.11

Patch Changes

2023.7.10

Patch Changes

  • Ensure storefrontRedirect fallback only redirects to relative URLs. (#1399) by @frandiox

2023.7.9

Patch Changes

  • Allow generic inference in standalone usage of WithCache type - Contributed by @chinanderm (#1363) by @wizardlyhel

  • Cart Optimistic UI helpers (#1366) by @wizardlyhel

  • Fix storefront sub request cache key (#1375) by @wizardlyhel

  • Fix the Pagination component to use forwardRefs for the NextLink and PreviousLink render props (#1362) by @blittle

2023.7.8

Patch Changes

  • The error.cause property throw from the Storefront client is now stringified. (#1184) by @frandiox

  • Fix Hydrogen's Storefront API client to not throw unhandled promise exceptions. This is because Remix is guaranteed to handle exceptions from the loader and fixing it prevents Hydrogen from crashing when deployed to some runtimes on unhandled promise exceptions. (#1318) by @blittle

  • Relax prop validation on the getSelectedProductOptions and getSelectedProductOptions utilities to look for member props instead of checking with instanceof. (#1327) by @blittle

2023.7.7

Patch Changes

  • Supress the hydration warning in the new <Script> component when nonce values differ between the server and client, which is expected. (#1312) by @frandiox

  • (Unstable) server-side network request debug virtual route (#1284) by @wizardlyhel

    1. Update your server.ts so that it also passes in the waitUntil and env.

        const handleRequest = createRequestHandler({
          build: remixBuild,
          mode: process.env.NODE_ENV,
      +    getLoadContext: () => ({session, storefront, env, waitUntil}),
        });

      If you are using typescript, make sure to update remix.env.d.ts

        declare module '@shopify/remix-oxygen' {
          export interface AppLoadContext {
      +     env: Env;
            cart: HydrogenCart;
            storefront: Storefront;
            session: HydrogenSession;
      +      waitUntil: ExecutionContext['waitUntil'];
          }
        }
    2. Run npm run dev and you should see terminal log information about a new virtual route that you can view server-side network requests at http://localhost:3000/debug-network

    3. Open http://localhost:3000/debug-network in a tab and your app another tab. When you navigate around your app, you should see server network requests being logged in the debug-network tab

2023.7.6

Patch Changes

2023.7.5

Patch Changes

  • Fix the Pagination component to reset internal state when the URL changes (not including Pagination params). (#1291) by @blittle

    We also now validate the connection prop to include a pageInfo object with the following properties:

    1. hasNextPage
    2. hasPreviousPage
    3. endCursor
    4. startCursor

    Previously our templates had a bug where startCursor was not included. Upgrading means the app will error until you update your query to include it:

     query CollectionDetails {
       collection(handle: $handle) {
         ...
         pageInfo {
           hasPreviousPage
           hasNextPage
           hasNextPage
           endCursor
    +      startCursor
         }
       }
     }
    

2023.7.4

Patch Changes

2023.7.3

Patch Changes

2023.7.2

Patch Changes

2023.7.1

Patch Changes

2023.7.0

What’s new

⭐️ Check out our blog post with all the latest updates on Hydrogen, and what’s coming on the roadmap.

The latest version of Hydrogen comes with new and updated components and utilities that can help you speed up your build:

  • An updated server-side Cart component with built-in abstractions to handle most common cart operations, including adding, updating, or deleting line items, applying discounts, and more.
  • A drop-in <Pagination/> component to make it easier to handle large product collections.
  • A new <VariantSelector/> component that makes it faster to build progressively enhanced product forms.
  • Improved support for predictive search and local pickup options through Storefront API version 2023-07.

Breaking Changes

  • createWithCache is now stable. All imports need to be updated: (#1151) by @blittle

    - import {createWithCache_unstable} from '@shopify/hydrogen';
    + import {createWithCache} from '@shopify/hydrogen';
  • Pagination and getPaginationVariables are now stable. (#1129) by @blittle

    All imports to each should be updated:

    - import {Pagiatinon__unstable, getPaginationVariables__unstable} from '@shopify/hydrogen';
    + import {Pagiatinon, getPaginationVariables} from '@shopify/hydrogen';

Patch Changes

Add a <VariantSelector> component to make building product forms easier. Also added the getSelectedProductOptions helper function. See the guide on using the VariantSelector. (#1027) by @blittle

2023.4.6

Patch Changes

2023.4.5

Patch Changes

  • Update Remix to the latest version (1.17.1). (#852) by @frandiox

    When updating your app, remember to also update your Remix dependencies to 1.17.1 in your package.json file:

    -"@remix-run/react": "1.15.0",
    +"@remix-run/react": "1.17.1",
    
    -"@remix-run/dev": "1.15.0",
    -"@remix-run/eslint-config": "1.15.0",
    +"@remix-run/dev": "1.17.1",
    +"@remix-run/eslint-config": "1.17.1",

2023.4.4

Patch Changes

  • Fix redirects to respond with a 301 (#946) by @blittle

  • A default https:// protocol is now added automatically to storeDomain if missing. (#985) by @frandiox

  • Fix flattenConnection()'s TypeScript types when working with edges.node (#945) by @frehner

  • Make storefrontApiVersion parameter optional. By default, it will use the current version of Hydrogen as the Storefront API version. (#984) by @frandiox

  • Skip reading and writing cache in sub-requests when the strategy is CacheNone. (#964) by @frandiox

  • Fix <ModelViewer> to properly set className (#966) by @blittle

  • Add a /admin route that redirects to the Shopify admin. This redirect can be disabled by passing noAdminRedirect: true to storefrontRedirect: (#989) by @blittle

    storefrontRedirect({
      redirect,
      response,
      storefront,
      noAdminRedirect: true,
    });
  • Updated dependencies [7b4afea2, 32515232, 7d6a1a7c, 442f602a, b9ab8eb7, 93a7c3c6]:

2023.4.3

Patch Changes

2023.4.2

Patch Changes

  • Add support for generated types from the new unstable codegen feature in the CLI. (#707) by @frandiox

  • Add a <Pagination__unstable> component and getPaginationVariables__unstable helper to make rendering large lists from the Storefront API easy. This is an initial unstable release and we expect to finalize the API by the 2023-07 release. See the <Pagination> component documentation. (#755) by @cartogram

  • Updated dependencies [2e1e4590]:

2023.4.1

Patch Changes

  • Adds parseGid() which is a helper function that takes in a Shopify GID and returns the resource and id from it. For example: (#845) by @frehner

    import {parseGid} from '@shopify/hydrogen-react';
    
    const {id, resource} = parseGid('gid://shopify/Order/123');
    
    console.log(id); // 123
    console.log(resource); // Order
  • Avoid warning about missing buyerIp when using private access tokens in development. (#836) by @frandiox

  • Updated dependencies [0a009a3b]:

2023.4.0

Major Changes

  • Releases 2023-04 (#754) by @lordofthecactus

  • Updates Hydrogen to Storefront 2023-04 API release.

  • Updates types from CartLineConnection to BaseCartLineConnection.

  • Deprecates CartLinePrice from @shopify/hydrogen-react use Money instead:

    - import {CartLinePrice} from '@shopify/hydrogen-react';
    + import {Money} from '@shopify/hydrogen-react';
    - <CartLinePrice line={line} />
    + <Money data={line.priceV2} />

    Check the docs for using Money 💵.

  • Adds a new Image component, replacing the existing one. While your existing implementation won't break, props widths and loaderOptions are now deprecated disregarded, with a new aspectRatio prop added. (#787) by @benjaminsehl

    Migrating to the new Image

    The new Image component is responsive by default, and requires less configuration to ensure the right image size is being rendered on all screen sizes.

    Before

    <Image
      data={image}
      widths={[400, 800, 1200]}
      width="100px"
      sizes="90vw"
      loaderOptions={{
        scale: 2,
        crop: 'left',
      }}
    />

    After

    <Image data={image} sizes="90vw" crop="left" aspectRatio="3/2" />

    Note that widths and loaderOptions have now been deprecated, declaring width is no longer necessary, and we’ve added an aspectRatio prop:

    • widths is now calculated automatically based on a new srcSetOptions prop (see below for details).
    • loaderOptions has been removed in favour of declaring crop and src as props. width and height should only be set as props if rendering a fixed image size, with width otherwise defaulting to 100%, and the loader calculating each dynamically.
    • aspectRatio is calculated automatically using data.width and data.height (if available) — but if you want to present an image with an aspect ratio other than what was uploaded, you can set using the format Int/Int (e.g. 3/2, see MDN docs for more info, note that you must use the fraction style of declaring aspect ratio, decimals are not supported); if you've set an aspectRatio, we will default the crop to be crop: center (in the example above we've specified this to use left instead).

    Examples

    Basic Usage

    <Image data={data} />

    This would use all default props, which if exhaustively declared would be the same as typing:

    <Image
      data={data}
      crop="center"
      decoding="async"
      loading="lazy"
      width="100%"
      sizes="100vw"
      srcSetOptions={{
        interval: 15,
        startingWidth: 200,
        incrementSize: 200,
        placeholderWidth: 100,
      }}
    />

    An alternative way to write this without using data would be to use the src, alt, and aspectRatio props. For example:

    <Image
      src={data.url}
      alt={data.altText}
      aspectRatio={`${data.width}/${data.height}`}
    />

    Assuming data had the following shape:

    {
      "url": "https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg",
      "altText": "alt text",
      "width": "4000",
      "height": "4000"
    }

    All three above examples would result in the following HTML:

    <img
      srcset="https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=300&height=300&crop=center 300w, … *13 additional sizes* … https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=3000&height=3000&crop=center 3000w"
      src="https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=100&height=100&crop=center"
      alt="alt text"
      sizes="100vw"
      loading="lazy"
      decoding="async"
      width="100px"
      height="100px"
      style="aspect-ratio: 4000 / 4000;"
    />

    Fixed-size Images

    When using images that are meant to be a fixed size, like showing a preview image of a product in the cart, instead of using aspectRatio, you'll instead declare width and height manually with fixed values. For example:

    <Image data={data} width={80} height={80} />

    Instead of generating 15 images for a broad range of screen sizes, Image will instead only generate 3, for various screen pixel densities (1x, 2x, and 3x). The above example would result in the following HTML:

    <img
      srcset="
        https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=80&height=80&crop=center   1x,
        https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=160&height=160&crop=center 2x,
        https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=240&height=240&crop=center 3x
      "
      src="https://cdn.shopify.com/s/files/1/0551/4566/0472/products/Main.jpg?width=80&height=80"
      alt="alt text"
      loading="lazy"
      width="80px"
      height="80px"
      style="aspect-ratio: 80 / 80;"
    />

    If you don't want to have a fixed aspect ratio, and instead respect whatever is returned from your query, the following syntax can also be used:

    <Image data={data} width="5rem" />

    Which would result in the same HTML as above, however the generated URLs inside the src and srcset attributes would not have height or crop parameters appended to them, and the generated aspect-ratio in style would be 4000 / 4000 (if using the same data values as our original example).

    Custom Loaders

    If your image isn't coming from the Storefront API, but you still want to take advantage of the Image component, you can pass a custom loader prop, provided the CDN you're working with supports URL-based transformations.

    The loader is a function which expects a params argument of the following type:

    type LoaderParams = {
      /** The base URL of the image */
      src?: ImageType['url'];
      /** The URL param that controls width */
      width?: number;
      /** The URL param that controls height */
      height?: number;
      /** The URL param that controls the cropping region */
      crop?: Crop;
    };

    Here is an example of using Image with a custom loader function:

    const customLoader = ({src, width, height, crop}) => {
      return `${src}?w=${width}&h=${height}&gravity=${crop}`;
    };
    
    export default function CustomImage(props) {
      <Image loader={customLoader} {...props} />;
    }
    
    // In Use:
    
    <CustomImage data={customCDNImageData} />;

    If your CDN happens to support the same semantics as Shopify (URL params of width, height, and crop) — the default loader will work a non-Shopify src attribute.

    An example output might look like: https://mycdn.com/image.jpeg?width=100&height=100&crop=center

    Additional changes

    • Added the srcSetOptions prop used to create the image URLs used in srcset. It’s an object with the following keys and defaults:

      srcSetOptions = {
        intervals: 15, // The number of sizes to generate
        startingWidth: 200, // The smalles image size
        incrementSize: 200, // The increment by to increase for each size, in pixesl
        placeholderWidth: 100, // The size used for placeholder fallback images
      };
    • Added an export for IMAGE_FRAGMENT, which can be imported from Hydrogen and used in any Storefront API query, which will fetch the required fields needed by the component.

    • Added an export for shopifyLoader for using Storefront API responses in conjunction with alternative frameworks that already have their own Image component, like Next.js

Patch Changes

2023.1.7

Patch Changes

  • Bump internal Remix dependencies to 1.15.0. (#728) by @wizardlyhel

    Recommendations to follow:

    • Upgrade all the Remix packages in your app to 1.15.0.
    • Enable Remix v2 future flags at your earliest convenience following the official guide.
  • Add an experimental createWithCache_unstable utility, which creates a function similar to useQuery from Hydrogen v1. Use this utility to query third-party APIs and apply custom cache options. (#600) by @frandiox

    To setup the utility, update your server.ts:

    import {
      createStorefrontClient,
      createWithCache_unstable,
      CacheLong,
    } from '@shopify/hydrogen';
    
    // ...
    
      const cache = await caches.open('hydrogen');
      const withCache = createWithCache_unstable({cache, waitUntil});
    
      // Create custom utilities to query third-party APIs:
      const fetchMyCMS = (query) => {
        // Prefix the cache key and make it unique based on arguments.
        return withCache(['my-cms', query], CacheLong(), () => {
          const cmsData = await (await fetch('my-cms.com/api', {
            method: 'POST',
            body: query
          })).json();
    
          const nextPage = (await fetch('my-cms.com/api', {
            method: 'POST',
            body: cmsData1.nextPageQuery,
          })).json();
    
          return {...cmsData, nextPage}
        });
      };
    
      const handleRequest = createRequestHandler({
        build: remixBuild,
        mode: process.env.NODE_ENV,
        getLoadContext: () => ({
          session,
          waitUntil,
          storefront,
          env,
          fetchMyCMS,
        }),
      });

    Note: The utility is unstable and subject to change before stabalizing in the 2023.04 release.

  • Updated dependencies [85ae63a, 5e26503]:

2023.1.6

Patch Changes

  • Add new loader API for setting seo tags within route module (#591) by @cartogram

  • ShopPayButton component now can receive a storeDomain. The component now does not require ShopifyProvider. (#645) by @lordofthecactus

    1. Update Remix to 1.14.0 (#599) by @blittle

    2. Add Cache-Control defaults to all the demo store routes

  • Added robots option to SEO config that allows users granular control over the robots meta tag. This can be set on both a global and per-page basis using the handle.seo property. (#572) by @cartogram

    Example:

    export handle = {
      seo: {
        robots: {
          noIndex: false,
          noFollow: false,
        }
      }
    }
  • Fix active cart session event in Live View (#614) by @wizardlyhel

    Introducing getStorefrontHeaders that collects the required Shopify headers for making a Storefront API call.

    • Make cart constants available as exports from @shopify/hydrogen-react
    • Deprecating buyerIp and requestGroupId props from createStorefrontClient from @shopify/hydrogen
    • Deprecating getBuyerIp function from @shopify/remix-oxygen
    + import {getStorefrontHeaders} from '@shopify/remix-oxygen';
    import {createStorefrontClient, storefrontRedirect} from '@shopify/hydrogen';
    
    export default {
      async fetch(
        request: Request,
        env: Env,
        executionContext: ExecutionContext,
      ): Promise<Response> {
    
        const {storefront} = createStorefrontClient({
          cache,
          waitUntil,
    -     buyerIp: getBuyerIp(request),
          i18n: {language: 'EN', country: 'US'},
          publicStorefrontToken: env.PUBLIC_STOREFRONT_API_TOKEN,
          privateStorefrontToken: env.PRIVATE_STOREFRONT_API_TOKEN,
          storeDomain: `https://${env.PUBLIC_STORE_DOMAIN}`,
          storefrontApiVersion: env.PUBLIC_STOREFRONT_API_VERSION || '2023-01',
          storefrontId: env.PUBLIC_STOREFRONT_ID,
    -     requestGroupId: request.headers.get('request-id'),
    +     storefrontHeaders: getStorefrontHeaders(request),
        });
  • Updated dependencies [c78f441, 7fca5d5]:

2023.1.5

Patch Changes

2023.1.4

Patch Changes

  • Fix template imports to only reference @shopify/hydrogen, not @shopify/hydrogen-react (#523) by @blittle

2023.1.3

Patch Changes

  • Send Hydrogen version in Storefront API requests. (#471) by @frandiox

  • Fix default Storefront type in LoaderArgs. (#496) by @frandiox

2023.1.2

Patch Changes

  • Add license files and readmes for all packages (#463) by @blittle

2023.1.1

Patch Changes

  • Initial release